feat(auth): integrate authentication with installation lifecycle
This commit is contained in:
@@ -21,7 +21,9 @@ import (
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/lifecycle"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/service"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -130,6 +132,10 @@ func createWithDependencies(ctx context.Context, installation config.Installatio
|
||||
if err != nil {
|
||||
return Result{}, fmt.Errorf("installation external secret references could not be read: %w", err)
|
||||
}
|
||||
authenticationPaths, err := authenticationConfigFiles(installation)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
revision, err := dependencies.revision(ctx, installation.ProjectDirectory)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
@@ -208,12 +214,12 @@ func createWithDependencies(ctx context.Context, installation config.Installatio
|
||||
InstallationID: installationID,
|
||||
CreatedAt: dependencies.now().UTC(),
|
||||
SourceRevision: revision,
|
||||
IncludesSecrets: request.IncludeSecrets && len(secretPaths) > 0,
|
||||
IncludesSecrets: request.IncludeSecrets && len(secretPaths)+len(authenticationPaths) > 0,
|
||||
ComposeProject: installation.ProjectName(),
|
||||
Images: images,
|
||||
Volumes: volumes,
|
||||
}
|
||||
if err := writeArchive(ctx, output, reservation, installation, request, secretPaths, manifest, volumes, dependencies); err != nil {
|
||||
if err := writeArchive(ctx, output, reservation, installation, request, secretPaths, authenticationPaths, manifest, volumes, dependencies); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
published = true
|
||||
@@ -228,8 +234,8 @@ func createWithDependencies(ctx context.Context, installation config.Installatio
|
||||
maintenanceActive = false
|
||||
}
|
||||
result = Result{Path: output}
|
||||
if request.IncludeSecrets {
|
||||
result.Warning = "The archive contains external secret files. Protect its custody and access."
|
||||
if manifest.IncludesSecrets {
|
||||
result.Warning = "The archive contains external secret files, including authentication configuration. Protect its custody and access."
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -631,7 +637,7 @@ func runCompose(ctx context.Context, installation config.Installation, runner ar
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeArchive(ctx context.Context, output string, reservation *archiveReservation, installation config.Installation, request CreateRequest, secretPaths []string, manifest Manifest, volumes []VolumeMetadata, dependencies dependencies) (resultErr error) {
|
||||
func writeArchive(ctx context.Context, output string, reservation *archiveReservation, installation config.Installation, request CreateRequest, secretPaths, authenticationPaths []string, manifest Manifest, volumes []VolumeMetadata, dependencies dependencies) (resultErr error) {
|
||||
directory := filepath.Dir(output)
|
||||
temporary, err := os.CreateTemp(directory, ".tht-backup-*.tmp")
|
||||
if err != nil {
|
||||
@@ -732,6 +738,23 @@ func writeArchive(ctx context.Context, output string, reservation *archiveReserv
|
||||
SourcePath: source, SHA256: "sha256:" + hex.EncodeToString(hash.Sum(nil)), Size: size, Sensitive: true,
|
||||
})
|
||||
}
|
||||
for index, source := range authenticationPaths {
|
||||
name := fmt.Sprintf("authentication-secrets/%03d-%s", index, filepath.Base(source))
|
||||
if request.IncludeSecrets {
|
||||
if err := addFile(name, "authentication-configuration", true, source); err != nil {
|
||||
return err
|
||||
}
|
||||
entry := &manifest.Entries[len(manifest.Entries)-1]
|
||||
entry.Kind, entry.SourcePath, entry.Sensitive = EntryExternalSecret, source, true
|
||||
continue
|
||||
}
|
||||
if filepath.Base(source) != "auth.yaml" {
|
||||
continue
|
||||
}
|
||||
if err := addAuthenticationReference(&manifest, name, source); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
headerName := "volumes/" + volume.LogicalName + ".tar"
|
||||
header := &zip.FileHeader{Name: headerName, Method: zip.Deflate}
|
||||
@@ -804,6 +827,51 @@ func configurationInputs(installation config.Installation) []configurationInput
|
||||
return inputs
|
||||
}
|
||||
|
||||
const maxAuthenticationConfigurationBytes = 1 << 20
|
||||
|
||||
func authenticationConfigFiles(installation config.Installation) ([]string, error) {
|
||||
directory := installation.AuthenticationDirectory()
|
||||
if directory == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if err := safeio.ValidatePrivateDirectory(directory); err != nil {
|
||||
return nil, errors.New("authentication configuration directory is unavailable or unsafe")
|
||||
}
|
||||
authPath := filepath.Join(directory, "auth.yaml")
|
||||
contents, err := safeio.ReadCanonicalRegular(authPath, maxAuthenticationConfigurationBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("authentication configuration is unavailable or unsafe")
|
||||
}
|
||||
var configuration struct {
|
||||
Mode string `yaml:"mode"`
|
||||
}
|
||||
if err := yaml.Unmarshal(contents, &configuration); err != nil || (configuration.Mode != "local" && configuration.Mode != "oidc") {
|
||||
return nil, errors.New("authentication configuration is unavailable or invalid")
|
||||
}
|
||||
paths := []string{authPath}
|
||||
if configuration.Mode == "local" {
|
||||
usersPath := filepath.Join(directory, "users.yaml")
|
||||
if _, err := safeio.ReadCanonicalRegular(usersPath, maxAuthenticationConfigurationBytes); err != nil {
|
||||
return nil, errors.New("authentication user registry is unavailable or unsafe")
|
||||
}
|
||||
paths = append(paths, usersPath)
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func addAuthenticationReference(manifest *Manifest, name, source string) error {
|
||||
contents, err := safeio.ReadCanonicalRegular(source, maxAuthenticationConfigurationBytes)
|
||||
if err != nil {
|
||||
return errors.New("authentication configuration is unavailable or unsafe")
|
||||
}
|
||||
digest := sha256.Sum256(contents)
|
||||
manifest.Entries = append(manifest.Entries, Entry{
|
||||
Path: name, Kind: EntrySecretReference, Owner: "authentication-configuration", SourcePath: source,
|
||||
SHA256: "sha256:" + hex.EncodeToString(digest[:]), Size: int64(len(contents)), Sensitive: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeArchiveCommand(volume string) []string {
|
||||
return []string{"run", "--rm", "--network", "none", "--mount", "type=volume,src=" + volume + ",dst=/source,readonly", helperImage, "tar", "--numeric-owner", "-C", "/source", "-cf", "-", "."}
|
||||
}
|
||||
@@ -837,6 +905,10 @@ func archivePreservationRoots(installation config.Installation, secretPaths []st
|
||||
if err != nil || !info.IsDir() {
|
||||
return errors.New("server preservation root is unavailable")
|
||||
}
|
||||
authStateRoot := ""
|
||||
if variable == "THT_DATA_ROOT" {
|
||||
authStateRoot = filepath.Join(root, "auth")
|
||||
}
|
||||
err = filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
@@ -847,6 +919,12 @@ func archivePreservationRoots(installation config.Installation, secretPaths []st
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if authStateRoot != "" && path == authStateRoot {
|
||||
if entry.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if entry.Type()&os.ModeSymlink != 0 {
|
||||
return errors.New("server preservation root contains a symlink")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user