package filesystem import ( "bytes" "encoding/pem" "errors" "fmt" "io" "os" "path/filepath" "strings" portainer "github.com/portainer/portainer/api" "github.com/gofrs/uuid" "github.com/rs/zerolog/log" "github.com/segmentio/encoding/json" ) const ( // TLSStorePath represents the subfolder where TLS files are stored in the file store folder. TLSStorePath = "tls" // LDAPStorePath represents the subfolder where LDAP TLS files are stored in the TLSStorePath. LDAPStorePath = "ldap" // TLSCACertFile represents the name on disk for a TLS CA file. TLSCACertFile = "ca.pem" // TLSCertFile represents the name on disk for a TLS certificate file. TLSCertFile = "cert.pem" // TLSKeyFile represents the name on disk for a TLS key file. TLSKeyFile = "key.pem" // ComposeStorePath represents the subfolder where compose files are stored in the file store folder. ComposeStorePath = "compose" // ComposeFileDefaultName represents the default name of a compose file. ComposeFileDefaultName = "docker-compose.yml" // ManifestFileDefaultName represents the default name of a k8s manifest file. ManifestFileDefaultName = "k8s-deployment.yml" // EdgeStackStorePath represents the subfolder where edge stack files are stored in the file store folder. EdgeStackStorePath = "edge_stacks" // PrivateKeyFile represents the name on disk of the file containing the private key. PrivateKeyFile = "portainer.key" // PublicKeyFile represents the name on disk of the file containing the public key. PublicKeyFile = "portainer.pub" // BinaryStorePath represents the subfolder where binaries are stored in the file store folder. BinaryStorePath = "bin" // EdgeJobStorePath represents the subfolder where schedule files are stored. EdgeJobStorePath = "edge_jobs" // DockerConfigPath represents the subfolder where docker configuration is stored. DockerConfigPath = "docker_config" // ExtensionRegistryManagementStorePath represents the subfolder where files related to the // registry management extension are stored. ExtensionRegistryManagementStorePath = "extensions" // CustomTemplateStorePath represents the subfolder where custom template files are stored in the file store folder. CustomTemplateStorePath = "custom_templates" // TempPath represent the subfolder where temporary files are saved TempPath = "tmp" // SSLCertPath represents the default ssl certificates path SSLCertPath = "certs" // SSLCertFilename represents the ssl certificate file name SSLCertFilename = "cert.pem" // SSLKeyFilename represents the ssl key file name SSLKeyFilename = "key.pem" // SSLCACertFilename represents the CA ssl certificate file name for mTLS SSLCACertFilename = "ca-cert.pem" MTLSCertFilename = "mtls-cert.pem" MTLSCACertFilename = "mtls-ca-cert.pem" MTLSKeyFilename = "mtls-key.pem" // ChiselPath represents the default chisel path ChiselPath = "chisel" // ChiselPrivateKeyFilename represents the chisel private key file name ChiselPrivateKeyFilename = "private-key.pem" ) // ErrUndefinedTLSFileType represents an error returned on undefined TLS file type var ErrUndefinedTLSFileType = errors.New("Undefined TLS file type") // Service represents a service for managing files and directories. type Service struct { dataStorePath string fileStorePath string } // JoinPaths takes a trusted root path and a list of untrusted paths and joins // them together using directory separators while enforcing that the untrusted // paths cannot go higher up than the trusted root func JoinPaths(trustedRoot string, untrustedPaths ...string) string { if trustedRoot == "" { trustedRoot = "." } p := filepath.Join(trustedRoot, filepath.Join(append([]string{"/"}, untrustedPaths...)...)) // avoid setting a volume name from the untrusted paths vnp := filepath.VolumeName(p) if filepath.VolumeName(trustedRoot) == "" && vnp != "" { return strings.TrimPrefix(strings.TrimPrefix(p, vnp), `\`) } return p } // NewService initializes a new service. It creates a data directory and a directory to store files // inside this directory if they don't exist. func NewService(dataStorePath, fileStorePath string) (*Service, error) { service := &Service{ dataStorePath: dataStorePath, fileStorePath: JoinPaths(dataStorePath, fileStorePath), } err := os.MkdirAll(dataStorePath, 0755) if err != nil { return nil, err } err = service.createDirectoryInStore(SSLCertPath) if err != nil { return nil, err } err = service.createDirectoryInStore(TLSStorePath) if err != nil { return nil, err } err = service.createDirectoryInStore(ComposeStorePath) if err != nil { return nil, err } err = service.createDirectoryInStore(BinaryStorePath) if err != nil { return nil, err } err = service.createDirectoryInStore(DockerConfigPath) if err != nil { return nil, err } return service, nil } // GetBinaryFolder returns the full path to the binary store on the filesystem func (service *Service) GetBinaryFolder() string { return JoinPaths(service.fileStorePath, BinaryStorePath) } // GetDockerConfigPath returns the full path to the docker config store on the filesystem func (service *Service) GetDockerConfigPath() string { return JoinPaths(service.fileStorePath, DockerConfigPath) } // RemoveDirectory removes a directory on the filesystem. func (service *Service) RemoveDirectory(directoryPath string) error { return os.RemoveAll(directoryPath) } // GetStackProjectPath returns the absolute path on the FS for a stack based // on its identifier. func (service *Service) GetStackProjectPath(stackIdentifier string) string { return JoinPaths(service.wrapFileStore(ComposeStorePath), stackIdentifier) } // GetStackProjectPathByVersion returns the absolute path on the FS for a stack based // on its identifier and version. func (service *Service) GetStackProjectPathByVersion(stackIdentifier string, version int, commitHash string) string { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } if commitHash != "" { versionStr = commitHash } return JoinPaths(service.wrapFileStore(ComposeStorePath), stackIdentifier, versionStr) } // Copy copies the file on fromFilePath to toFilePath // if toFilePath exists func will fail unless deleteIfExists is true func (service *Service) Copy(fromFilePath string, toFilePath string, deleteIfExists bool) error { exists, err := service.FileExists(fromFilePath) if err != nil { return err } if !exists { return fmt.Errorf("File (%s) doesn't exist", fromFilePath) } finput, err := os.Open(fromFilePath) if err != nil { return err } defer finput.Close() exists, err = service.FileExists(toFilePath) if err != nil { return err } if exists { if !deleteIfExists { return errors.New("Destination file exists") } err := os.Remove(toFilePath) if err != nil { return err } } foutput, err := os.Create(toFilePath) if err != nil { return err } defer foutput.Close() buf := make([]byte, 1024) for { n, err := finput.Read(buf) if err != nil && err != io.EOF { return err } if n == 0 { break } if _, err := foutput.Write(buf[:n]); err != nil { return err } } return nil } // StoreStackFileFromBytes creates a subfolder in the ComposeStorePath and stores a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) StoreStackFileFromBytes(stackIdentifier, fileName string, data []byte) (string, error) { stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier) err := service.createDirectoryInStore(stackStorePath) if err != nil { return "", err } composeFilePath := JoinPaths(stackStorePath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(composeFilePath, r) if err != nil { return "", err } return service.wrapFileStore(stackStorePath), nil } // StoreStackFileFromBytesByVersion creates a version subfolder in the ComposeStorePath and stores a new file from bytes. // It returns the path to the folder where version folders are stored. func (service *Service) StoreStackFileFromBytesByVersion(stackIdentifier, fileName string, version int, data []byte) (string, error) { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier) stackVersionPath := JoinPaths(stackStorePath, versionStr) err := service.createDirectoryInStore(stackVersionPath) if err != nil { return "", err } composeFilePath := JoinPaths(stackVersionPath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(composeFilePath, r) if err != nil { return "", err } return service.wrapFileStore(stackStorePath), nil } // UpdateStoreStackFileFromBytes makes stack file backup and updates a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) UpdateStoreStackFileFromBytes(stackIdentifier, fileName string, data []byte) (string, error) { stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier) composeFilePath := JoinPaths(stackStorePath, fileName) err := service.createBackupFileInStore(composeFilePath) if err != nil { return "", err } r := bytes.NewReader(data) err = service.createFileInStore(composeFilePath, r) if err != nil { return "", err } return service.wrapFileStore(stackStorePath), nil } // UpdateStoreStackFileFromBytesByVersion makes stack file backup and updates a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) UpdateStoreStackFileFromBytesByVersion(stackIdentifier, fileName string, version int, commitHash string, data []byte) (string, error) { stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier) versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } if commitHash != "" { versionStr = commitHash } if versionStr != "" { stackStorePath = JoinPaths(stackStorePath, versionStr) } composeFilePath := JoinPaths(stackStorePath, fileName) err := service.createBackupFileInStore(composeFilePath) if err != nil { return "", err } r := bytes.NewReader(data) err = service.createFileInStore(composeFilePath, r) if err != nil { return "", err } return service.wrapFileStore(stackStorePath), nil } // RemoveStackFileBackup removes the stack file backup in the ComposeStorePath. func (service *Service) RemoveStackFileBackup(stackIdentifier, fileName string) error { stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier) composeFilePath := JoinPaths(stackStorePath, fileName) return service.removeBackupFileInStore(composeFilePath) } // RemoveStackFileBackupByVersion removes the stack file backup by version in the ComposeStorePath. func (service *Service) RemoveStackFileBackupByVersion(stackIdentifier string, version int, fileName string) error { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier, versionStr) composeFilePath := JoinPaths(stackStorePath, fileName) return service.removeBackupFileInStore(composeFilePath) } // RollbackStackFile rollbacks the stack file backup in the ComposeStorePath. func (service *Service) RollbackStackFile(stackIdentifier, fileName string) error { stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier) composeFilePath := JoinPaths(stackStorePath, fileName) path := service.wrapFileStore(composeFilePath) backupPath := fmt.Sprintf("%s.bak", path) exists, err := service.FileExists(backupPath) if err != nil { return err } if !exists { // keep the updated/failed stack file return nil } err = service.Copy(backupPath, path, true) if err != nil { return err } return os.Remove(backupPath) } // RollbackStackFileByVersion rollbacks the stack file backup by version in the ComposeStorePath. func (service *Service) RollbackStackFileByVersion(stackIdentifier string, version int, fileName string) error { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } stackStorePath := JoinPaths(ComposeStorePath, stackIdentifier, versionStr) composeFilePath := JoinPaths(stackStorePath, fileName) path := service.wrapFileStore(composeFilePath) backupPath := fmt.Sprintf("%s.bak", path) exists, err := service.FileExists(backupPath) if err != nil { return err } if !exists { // keep the updated/failed stack file return nil } err = service.Copy(backupPath, path, true) if err != nil { return err } return os.Remove(backupPath) } // GetEdgeStackProjectPath returns the absolute path on the FS for a edge stack based // on its identifier. func (service *Service) GetEdgeStackProjectPath(edgeStackIdentifier string) string { return JoinPaths(service.wrapFileStore(EdgeStackStorePath), edgeStackIdentifier) } // StoreEdgeStackFileFromBytes creates a subfolder in the EdgeStackStorePath and stores a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) StoreEdgeStackFileFromBytes(edgeStackIdentifier, fileName string, data []byte) (string, error) { stackStorePath := JoinPaths(EdgeStackStorePath, edgeStackIdentifier) err := service.createDirectoryInStore(stackStorePath) if err != nil { return "", err } composeFilePath := JoinPaths(stackStorePath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(composeFilePath, r) if err != nil { return "", err } return service.wrapFileStore(stackStorePath), nil } // GetEdgeStackProjectPathByVersion returns the absolute path on the FS for a edge stack based // on its identifier and version. // EE only feature func (service *Service) GetEdgeStackProjectPathByVersion(edgeStackIdentifier string, version int, commitHash string) string { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } if commitHash != "" { versionStr = commitHash } return JoinPaths(service.wrapFileStore(EdgeStackStorePath), edgeStackIdentifier, versionStr) } // StoreEdgeStackFileFromBytesByVersion creates a subfolder in the EdgeStackStorePath with version and stores a new file from bytes. // It returns the path to the folder where the file is stored. // EE only feature func (service *Service) StoreEdgeStackFileFromBytesByVersion(edgeStackIdentifier, fileName string, version int, data []byte) (string, error) { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } stackStorePath := JoinPaths(EdgeStackStorePath, edgeStackIdentifier, versionStr) err := service.createDirectoryInStore(stackStorePath) if err != nil { return "", err } composeFilePath := JoinPaths(stackStorePath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(composeFilePath, r) if err != nil { return "", err } return service.wrapFileStore(stackStorePath), nil } // FormProjectPathByVersion returns the absolute path on the FS for a project based with version func (service *Service) FormProjectPathByVersion(path string, version int, commitHash string) string { versionStr := "" if version != 0 { versionStr = fmt.Sprintf("v%d", version) } if commitHash != "" { versionStr = commitHash } return JoinPaths(path, versionStr) } // StoreRegistryManagementFileFromBytes creates a subfolder in the // ExtensionRegistryManagementStorePath and stores a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) StoreRegistryManagementFileFromBytes(folder, fileName string, data []byte) (string, error) { extensionStorePath := JoinPaths(ExtensionRegistryManagementStorePath, folder) err := service.createDirectoryInStore(extensionStorePath) if err != nil { return "", err } file := JoinPaths(extensionStorePath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(file, r) if err != nil { return "", err } return service.wrapFileStore(file), nil } // StoreTLSFileFromBytes creates a folder in the TLSStorePath and stores a new file from bytes. // It returns the path to the newly created file. func (service *Service) StoreTLSFileFromBytes(folder string, fileType portainer.TLSFileType, data []byte) (string, error) { storePath := JoinPaths(TLSStorePath, folder) err := service.createDirectoryInStore(storePath) if err != nil { return "", err } var fileName string switch fileType { case portainer.TLSFileCA: fileName = TLSCACertFile case portainer.TLSFileCert: fileName = TLSCertFile case portainer.TLSFileKey: fileName = TLSKeyFile default: return "", ErrUndefinedTLSFileType } tlsFilePath := JoinPaths(storePath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(tlsFilePath, r) if err != nil { return "", err } return service.wrapFileStore(tlsFilePath), nil } // GetPathForTLSFile returns the absolute path to a specific TLS file for an environment(endpoint). func (service *Service) GetPathForTLSFile(folder string, fileType portainer.TLSFileType) (string, error) { var fileName string switch fileType { case portainer.TLSFileCA: fileName = TLSCACertFile case portainer.TLSFileCert: fileName = TLSCertFile case portainer.TLSFileKey: fileName = TLSKeyFile default: return "", ErrUndefinedTLSFileType } return JoinPaths(service.wrapFileStore(TLSStorePath), folder, fileName), nil } // DeleteTLSFiles deletes a folder in the TLS store path. func (service *Service) DeleteTLSFiles(folder string) error { storePath := JoinPaths(service.wrapFileStore(TLSStorePath), folder) return os.RemoveAll(storePath) } // DeleteTLSFile deletes a specific TLS file from a folder. func (service *Service) DeleteTLSFile(folder string, fileType portainer.TLSFileType) error { var fileName string switch fileType { case portainer.TLSFileCA: fileName = TLSCACertFile case portainer.TLSFileCert: fileName = TLSCertFile case portainer.TLSFileKey: fileName = TLSKeyFile default: return ErrUndefinedTLSFileType } filePath := JoinPaths(service.wrapFileStore(TLSStorePath), folder, fileName) return os.Remove(filePath) } // GetFileContent returns the content of a file as bytes. func (service *Service) GetFileContent(trustedRoot, filePath string) ([]byte, error) { content, err := os.ReadFile(JoinPaths(trustedRoot, filePath)) if err != nil { if filePath == "" { filePath = trustedRoot } return nil, fmt.Errorf("could not get the contents of the file '%s'", filePath) } return content, nil } // Rename renames a file or directory func (service *Service) Rename(oldPath, newPath string) error { return os.Rename(oldPath, newPath) } // WriteJSONToFile writes JSON to the specified file. func (service *Service) WriteJSONToFile(path string, content any) error { jsonContent, err := json.Marshal(content) if err != nil { return err } return os.WriteFile(path, jsonContent, 0644) } // FileExists checks for the existence of the specified file. func (service *Service) FileExists(filePath string) (bool, error) { return FileExists(filePath) } // KeyPairFilesExist checks for the existence of the key files. func (service *Service) KeyPairFilesExist() (bool, error) { privateKeyPath := JoinPaths(service.dataStorePath, PrivateKeyFile) exists, err := service.FileExists(privateKeyPath) if err != nil || !exists { return false, err } publicKeyPath := JoinPaths(service.dataStorePath, PublicKeyFile) exists, err = service.FileExists(publicKeyPath) if err != nil || !exists { return false, err } return true, nil } // StoreKeyPair store the specified keys content as PEM files on disk. func (service *Service) StoreKeyPair(private, public []byte, privatePEMHeader, publicPEMHeader string) error { err := service.createPEMFileInStore(private, privatePEMHeader, PrivateKeyFile) if err != nil { return err } return service.createPEMFileInStore(public, publicPEMHeader, PublicKeyFile) } // LoadKeyPair retrieve the content of both key files on disk. func (service *Service) LoadKeyPair() ([]byte, []byte, error) { privateKey, err := service.getContentFromPEMFile(PrivateKeyFile) if err != nil { return nil, nil, err } publicKey, err := service.getContentFromPEMFile(PublicKeyFile) if err != nil { return nil, nil, err } return privateKey, publicKey, nil } // createDirectoryInStore creates a new directory in the file store func (service *Service) createDirectoryInStore(name string) error { path := service.wrapFileStore(name) return os.MkdirAll(path, 0700) } // createFile creates a new file in the file store with the content from r. func (service *Service) createFileInStore(filePath string, r io.Reader) error { path := service.wrapFileStore(filePath) return CreateFile(path, r) } // createBackupFileInStore makes a copy in the file store. func (service *Service) createBackupFileInStore(filePath string) error { path := service.wrapFileStore(filePath) backupPath := fmt.Sprintf("%s.bak", path) return service.Copy(path, backupPath, true) } // removeBackupFileInStore removes the copy in the file store. func (service *Service) removeBackupFileInStore(filePath string) error { path := service.wrapFileStore(filePath) backupPath := fmt.Sprintf("%s.bak", path) exists, err := service.FileExists(backupPath) if err != nil { return err } if exists { return os.Remove(backupPath) } return nil } func (service *Service) createPEMFileInStore(content []byte, fileType, filePath string) error { path := service.wrapFileStore(filePath) block := &pem.Block{Type: fileType, Bytes: content} out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } defer out.Close() return pem.Encode(out, block) } func (service *Service) getContentFromPEMFile(filePath string) ([]byte, error) { path := service.wrapFileStore(filePath) fileContent, err := os.ReadFile(path) if err != nil { return nil, err } block, _ := pem.Decode(fileContent) return block.Bytes, nil } // GetCustomTemplateProjectPath returns the absolute path on the FS for a custom template based // on its identifier. func (service *Service) GetCustomTemplateProjectPath(identifier string) string { return JoinPaths(service.wrapFileStore(CustomTemplateStorePath), identifier) } // StoreCustomTemplateFileFromBytes creates a subfolder in the CustomTemplateStorePath and stores a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) StoreCustomTemplateFileFromBytes(identifier, fileName string, data []byte) (string, error) { customTemplateStorePath := JoinPaths(CustomTemplateStorePath, identifier) err := service.createDirectoryInStore(customTemplateStorePath) if err != nil { return "", err } templateFilePath := JoinPaths(customTemplateStorePath, fileName) r := bytes.NewReader(data) err = service.createFileInStore(templateFilePath, r) if err != nil { return "", err } return service.wrapFileStore(customTemplateStorePath), nil } // GetEdgeJobFolder returns the absolute path on the filesystem for an Edge job based // on its identifier. func (service *Service) GetEdgeJobFolder(identifier string) string { return JoinPaths(service.wrapFileStore(EdgeJobStorePath), identifier) } // StoreEdgeJobFileFromBytes creates a subfolder in the EdgeJobStorePath and stores a new file from bytes. // It returns the path to the folder where the file is stored. func (service *Service) StoreEdgeJobFileFromBytes(identifier string, data []byte) (string, error) { edgeJobStorePath := JoinPaths(EdgeJobStorePath, identifier) err := service.createDirectoryInStore(edgeJobStorePath) if err != nil { return "", err } filePath := JoinPaths(edgeJobStorePath, createEdgeJobFileName(identifier)) r := bytes.NewReader(data) err = service.createFileInStore(filePath, r) if err != nil { return "", err } return service.wrapFileStore(filePath), nil } func createEdgeJobFileName(identifier string) string { return "job_" + identifier + ".sh" } // ClearEdgeJobTaskLogs clears the Edge job task logs func (service *Service) ClearEdgeJobTaskLogs(edgeJobID string, taskID string) error { path := service.getEdgeJobTaskLogPath(edgeJobID, taskID) return os.Remove(path) } // GetEdgeJobTaskLogFileContent fetches the Edge job task logs func (service *Service) GetEdgeJobTaskLogFileContent(edgeJobID string, taskID string) (string, error) { path := service.getEdgeJobTaskLogPath(edgeJobID, taskID) fileContent, err := os.ReadFile(path) if err != nil { return "", err } return string(fileContent), nil } // StoreEdgeJobTaskLogFileFromBytes stores the log file func (service *Service) StoreEdgeJobTaskLogFileFromBytes(edgeJobID, taskID string, data []byte) error { edgeJobStorePath := JoinPaths(EdgeJobStorePath, edgeJobID) err := service.createDirectoryInStore(edgeJobStorePath) if err != nil { return err } filePath := JoinPaths(edgeJobStorePath, fmt.Sprintf("logs_%s", taskID)) r := bytes.NewReader(data) return service.createFileInStore(filePath, r) } func (service *Service) getEdgeJobTaskLogPath(edgeJobID string, taskID string) string { return fmt.Sprintf("%s/logs_%s", service.GetEdgeJobFolder(edgeJobID), taskID) } // GetTemporaryPath returns a temp folder func (service *Service) GetTemporaryPath() (string, error) { uid, err := uuid.NewV4() if err != nil { return "", err } return JoinPaths(service.wrapFileStore(TempPath), uid.String()), nil } // GetDataStorePath returns path to data folder func (service *Service) GetDatastorePath() string { return service.dataStorePath } func (service *Service) wrapFileStore(filepath string) string { return JoinPaths(service.fileStorePath, filepath) } func defaultCertPathUnderFileStore() (string, string) { certPath := JoinPaths(SSLCertPath, SSLCertFilename) keyPath := JoinPaths(SSLCertPath, SSLKeyFilename) return certPath, keyPath } // GetDefaultSSLCertsPath returns the ssl certs path func (service *Service) GetDefaultSSLCertsPath() (string, string) { certPath, keyPath := defaultCertPathUnderFileStore() return service.wrapFileStore(certPath), service.wrapFileStore(keyPath) } func defaultMTLSCertPathUnderFileStore() (string, string, string) { certPath := JoinPaths(SSLCertPath, MTLSCertFilename) caCertPath := JoinPaths(SSLCertPath, MTLSCACertFilename) keyPath := JoinPaths(SSLCertPath, MTLSKeyFilename) return certPath, caCertPath, keyPath } // GetDefaultChiselPrivateKeyPath returns the chisle private key path func (service *Service) GetDefaultChiselPrivateKeyPath() string { privateKeyPath := defaultChiselPrivateKeyPathUnderFileStore() return service.wrapFileStore(privateKeyPath) } func defaultChiselPrivateKeyPathUnderFileStore() string { return JoinPaths(ChiselPath, ChiselPrivateKeyFilename) } // StoreChiselPrivateKey store the specified chisel private key content on disk. func (service *Service) StoreChiselPrivateKey(privateKey []byte) error { err := service.createDirectoryInStore(ChiselPath) if err != nil && !os.IsExist(err) { return err } r := bytes.NewReader(privateKey) privateKeyPath := defaultChiselPrivateKeyPathUnderFileStore() return service.createFileInStore(privateKeyPath, r) } // StoreSSLCertPair stores a ssl certificate pair func (service *Service) StoreSSLCertPair(cert, key []byte) (string, string, error) { certPath, keyPath := defaultCertPathUnderFileStore() r := bytes.NewReader(cert) err := service.createFileInStore(certPath, r) if err != nil { return "", "", err } r = bytes.NewReader(key) err = service.createFileInStore(keyPath, r) if err != nil { return "", "", err } return service.wrapFileStore(certPath), service.wrapFileStore(keyPath), nil } // CopySSLCertPair copies a ssl certificate pair func (service *Service) CopySSLCertPair(certPath, keyPath string) (string, string, error) { defCertPath, defKeyPath := service.GetDefaultSSLCertsPath() err := service.Copy(certPath, defCertPath, true) if err != nil { return "", "", err } err = service.Copy(keyPath, defKeyPath, true) if err != nil { return "", "", err } return defCertPath, defKeyPath, nil } // CopySSLCACert copies the specified caCert pem file func (service *Service) CopySSLCACert(caCertPath string) (string, error) { toFilePath := service.wrapFileStore(JoinPaths(SSLCertPath, SSLCACertFilename)) err := service.Copy(caCertPath, toFilePath, true) if err != nil { return "", err } return toFilePath, nil } // FileExists checks for the existence of the specified file. func FileExists(filePath string) (bool, error) { if _, err := os.Stat(filePath); err != nil { if os.IsNotExist(err) { return false, nil } return false, err } return true, nil } // SafeCopyDirectory copies a directory from src to dst in a safe way. func (service *Service) SafeMoveDirectory(originalPath, newPath string) error { // 1. Backup the source directory to a different folder backupDir := fmt.Sprintf("%s-%s", filepath.Dir(originalPath), "backup") err := MoveDirectory(originalPath, backupDir, false) if err != nil { return fmt.Errorf("failed to backup source directory: %w", err) } defer func() { if err != nil { // If an error occurred, rollback the backup directory restoreErr := restoreBackup(originalPath, backupDir) if restoreErr != nil { log.Warn().Err(restoreErr).Msg("failed to restore backup during creating versioning folder") } } }() // 2. Copy the backup directory to the destination directory err = CopyDir(backupDir, newPath, false) if err != nil { return fmt.Errorf("failed to copy backup directory to destination directory: %w", err) } // 3. Delete the backup directory err = os.RemoveAll(backupDir) if err != nil { return fmt.Errorf("failed to delete backup directory: %w", err) } return nil } func restoreBackup(src, backupDir string) error { // Rollback by deleting the original directory and copying the // backup direcotry back to the source directory, and then deleting // the backup directory err := os.RemoveAll(src) if err != nil { return fmt.Errorf("failed to delete destination directory: %w", err) } err = MoveDirectory(backupDir, src, false) if err != nil { return fmt.Errorf("failed to restore backup directory: %w", err) } return nil } func MoveDirectory(originalPath, newPath string, overwriteTargetPath bool) error { if _, err := os.Stat(originalPath); err != nil { return err } alreadyExists, err := FileExists(newPath) if err != nil { return err } if alreadyExists { if !overwriteTargetPath { return fmt.Errorf("Target path already exists") } if err = os.RemoveAll(newPath); err != nil { return fmt.Errorf("failed to overwrite path %s: %s", newPath, err.Error()) } } return os.Rename(originalPath, newPath) } func CreateFile(path string, r io.Reader) error { out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } defer out.Close() _, err = io.Copy(out, r) return err } func (service *Service) StoreMTLSCertificates(cert, caCert, key []byte) (string, string, string, error) { certPath, caCertPath, keyPath := defaultMTLSCertPathUnderFileStore() r := bytes.NewReader(cert) err := service.createFileInStore(certPath, r) if err != nil { return "", "", "", err } r = bytes.NewReader(caCert) err = service.createFileInStore(caCertPath, r) if err != nil { return "", "", "", err } r = bytes.NewReader(key) err = service.createFileInStore(keyPath, r) if err != nil { return "", "", "", err } return service.wrapFileStore(certPath), service.wrapFileStore(caCertPath), service.wrapFileStore(keyPath), nil }