442 lines
14 KiB
Go
442 lines
14 KiB
Go
|
package common
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"strings"
|
||
|
|
||
|
"git.oreonproject.org/oreonproject/eternity/lib"
|
||
|
|
||
|
"crypto/ed25519"
|
||
|
"crypto/x509"
|
||
|
"encoding/pem"
|
||
|
"path/filepath"
|
||
|
|
||
|
"github.com/fatih/color"
|
||
|
)
|
||
|
|
||
|
var DefaultLogger = lib.Logger{
|
||
|
LogFunc: func(log lib.Log) string {
|
||
|
var severityPretty string
|
||
|
switch log.Level {
|
||
|
case "INFO":
|
||
|
severityPretty = color.GreenString("[INFO]")
|
||
|
case "WARN":
|
||
|
severityPretty = color.YellowString("[WARN]")
|
||
|
case "ERROR":
|
||
|
severityPretty = color.HiYellowString("[ERROR]")
|
||
|
case "CRITICAL":
|
||
|
severityPretty = color.HiRedString("[CRITICAL]")
|
||
|
case "FATAL":
|
||
|
fmt.Println(color.RedString("[FATAL]"), log.Content)
|
||
|
os.Exit(1)
|
||
|
default:
|
||
|
severityPretty = color.WhiteString("[" + strings.ToUpper(log.Level) + "]")
|
||
|
}
|
||
|
fmt.Println(severityPretty, log.Content)
|
||
|
if log.Prompt {
|
||
|
fmt.Print(": ")
|
||
|
reader := bufio.NewReader(os.Stdin)
|
||
|
userInput, err := reader.ReadString('\n')
|
||
|
if err != nil {
|
||
|
fmt.Println(color.RedString("[FATAL]"), "Failed to read user input:", err)
|
||
|
os.Exit(1)
|
||
|
} else {
|
||
|
return userInput[:len(userInput)-1]
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
},
|
||
|
PromptSupported: true,
|
||
|
StdoutSupported: true,
|
||
|
Stdout: os.Stdout,
|
||
|
}
|
||
|
|
||
|
// EternityJsonHandleError handles errors related to the parsing of eternity.json
|
||
|
func EternityJsonHandleError(err error, vagueError error, logger *lib.Logger) {
|
||
|
switch {
|
||
|
case errors.Is(vagueError, lib.ErrEternityJsonOpenError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to open eternity.json: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrEternityJsonReadError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to reading eternity.json: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrEternityJsonParseError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to parse eternity.json: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrEternityJsonMapError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to map eternity.json: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// BuildEPKRemoveTemporaryDirectory removes the temporary directory created by BuildEPK
|
||
|
func BuildEPKRemoveTemporaryDirectory(tempDir string, logger *lib.Logger) {
|
||
|
err := os.RemoveAll(tempDir)
|
||
|
if err != nil {
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "ERROR",
|
||
|
Content: "Failed to cleanup temporary directory: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// BuildEPKHandleError handles errors related to the building of EPKs
|
||
|
func BuildEPKHandleError(tempDir string, err error, vagueError error, logger *lib.Logger) {
|
||
|
switch {
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKChrootError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Chroot containers are not yet supported",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKUnrestrictedError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Unrestricted builds are not yet supported",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKTemporaryDirectoryError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create temporary directory: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKCreateHooksError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create hooks directory: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKCopyHooksError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to copy hooks: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKBuildShError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create build.sh: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKWritingBuildShError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to write to build.sh: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKTargetRootError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create target root: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKExecutingBuildShError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to start BubbleWrap container and run build.sh in it: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKCountingFilesError):
|
||
|
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to count files in target root: " + err.Error(),
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrBuildEPKBadBuildType):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Invalid build type: " + err.Error(),
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// These errors are in the wrong order due to this function being rewritten.
|
||
|
// Oh well, not like it matters.
|
||
|
|
||
|
// PackageEPKHandleError handles errors related to the packaging of EPKs
|
||
|
func PackageEPKHandleError(err error, vagueError error, logger *lib.Logger) {
|
||
|
switch {
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKCreateDistDirError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create dist directory: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKMoveToDistError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to move to dist directory: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKTarError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create Tar archive: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKJSONMarshal):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to marshal JSON: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKCreateCompressionWriterError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create compression writer: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKCompressCloseError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to close compression writer: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKCannotOpenFile):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to open file for writing: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKCannotWriteFile):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to write to file: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKCannotCloseFile):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to close file: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKFailedToSeek):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to move file cursor: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrPackageEPKFailedToWriteHash):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to generate hash: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var LoadPrivateKeyHomedirError = errors.New("failed to get home directory")
|
||
|
var LoadPrivateKeyReadError = errors.New("failed to read private key")
|
||
|
var LoadPrivateKeyGenerateError = errors.New("failed to generate private key")
|
||
|
var LoadPrivateKeyMarshalError = errors.New("failed to marshal private key")
|
||
|
var LoadPrivateKeyFolderCreateError = errors.New("failed to create folder")
|
||
|
var LoadPrivateKeyWriteError = errors.New("failed to write private key")
|
||
|
var LoadPrivateKeyEmptyBlockError = errors.New("blocks returned by pem.Decode are empty")
|
||
|
var LoadPrivateKeyParseError = errors.New("failed to parse private key")
|
||
|
var LoadPrivateKeyNotEd25519Error = errors.New("private key is not ed25519")
|
||
|
|
||
|
// LoadPrivateKey loads the private key from $HOME/.local/share/eternity/private.key
|
||
|
func LoadPrivateKey(logger *lib.Logger) (ed25519.PrivateKey, error, error) {
|
||
|
// Attempt to read the key from $HOME/.local/share/eternity/private.key
|
||
|
homeDir, err := os.UserHomeDir()
|
||
|
if err != nil {
|
||
|
return nil, err, LoadPrivateKeyHomedirError
|
||
|
}
|
||
|
|
||
|
privateKeyBytes, err := os.ReadFile(filepath.Join(homeDir, ".local/share/eternity/private.key"))
|
||
|
if err != nil {
|
||
|
if !os.IsNotExist(err) {
|
||
|
return nil, err, LoadPrivateKeyReadError
|
||
|
} else {
|
||
|
response := logger.LogFunc(lib.Log{
|
||
|
Level: "INFO",
|
||
|
Content: "You need to add a private key to sign EPKs. Would you like to generate one now? (y/n)",
|
||
|
Prompt: true,
|
||
|
})
|
||
|
if strings.ToLower(response) == "y" {
|
||
|
_, privateKey, err := ed25519.GenerateKey(nil)
|
||
|
if err != nil {
|
||
|
return nil, err, LoadPrivateKeyGenerateError
|
||
|
}
|
||
|
|
||
|
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||
|
if err != nil {
|
||
|
return nil, err, LoadPrivateKeyMarshalError
|
||
|
}
|
||
|
|
||
|
privateKeyFile := pem.EncodeToMemory(&pem.Block{
|
||
|
Type: "ED25519 PRIVATE KEY",
|
||
|
Bytes: privateKeyBytes,
|
||
|
})
|
||
|
|
||
|
err = os.MkdirAll(filepath.Join(homeDir, ".local/share/eternity"), 0700)
|
||
|
if err != nil {
|
||
|
return nil, err, LoadPrivateKeyFolderCreateError
|
||
|
}
|
||
|
|
||
|
err = os.WriteFile(filepath.Join(homeDir, ".local/share/eternity/private.key"), privateKeyFile, 0600)
|
||
|
if err != nil {
|
||
|
return nil, err, LoadPrivateKeyWriteError
|
||
|
}
|
||
|
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "INFO",
|
||
|
Content: "Private key written to disk",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
|
||
|
return privateKey, nil, nil
|
||
|
} else if strings.ToLower(response) == "n" {
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "INFO",
|
||
|
Content: "Understood. Please add a ed25519 private key to " + filepath.Join(homeDir, ".local/share/eternity/private.key") + " in order to sign EPKs and repositories",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
block, _ := pem.Decode(privateKeyBytes)
|
||
|
if block == nil {
|
||
|
return nil, nil, LoadPrivateKeyEmptyBlockError
|
||
|
}
|
||
|
|
||
|
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||
|
if err != nil {
|
||
|
return nil, err, LoadPrivateKeyParseError
|
||
|
}
|
||
|
|
||
|
ed25519PrivateKey, ok := privateKey.(ed25519.PrivateKey)
|
||
|
if !ok {
|
||
|
return nil, nil, LoadPrivateKeyNotEd25519Error
|
||
|
}
|
||
|
|
||
|
return ed25519PrivateKey, nil, nil
|
||
|
}
|
||
|
|
||
|
// LoadPrivateKeyHandleError handles errors related to LoadPrivateKey
|
||
|
func LoadPrivateKeyHandleError(err error, vagueError error, logger *lib.Logger) {
|
||
|
switch {
|
||
|
case errors.Is(vagueError, LoadPrivateKeyHomedirError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to get home directory: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyReadError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to read private key: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyGenerateError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to generate private key: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyMarshalError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to marshal private key: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyFolderCreateError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to create eternity config folder (are you missing permissions?): " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyWriteError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to write private key to disk: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyEmptyBlockError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Blocks returned by pem.Decode are empty",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyParseError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to parse private key: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, LoadPrivateKeyNotEd25519Error):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Private key is not a ed25519 key",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func GenerateRepositoryHandleError(err error, vagueError error, logger *lib.Logger) {
|
||
|
switch {
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryStatError):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to stat directory: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryNotDirectory):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Specified path is not a directory",
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryFailedToWalk):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to walk directory: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotMarshalJSON):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to marshal JSON: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotOpenFile):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to open file for writing: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotWriteFile):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to write to file: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotCloseFile):
|
||
|
logger.LogFunc(lib.Log{
|
||
|
Level: "FATAL",
|
||
|
Content: "Failed to close file: " + err.Error(),
|
||
|
Prompt: false,
|
||
|
})
|
||
|
}
|
||
|
}
|