Files
ThothII/tools/tht/internal/preparation/credentials.go
T

74 lines
3.4 KiB
Go

package preparation
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/aritmolab/thothii/tools/tht/internal/authconfig"
"github.com/aritmolab/thothii/tools/tht/internal/config"
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
)
// Credentials creates installation-owned secrets only. External credentials are supplied by
// the operator. Existing files are checked and retained so interrupted preparation can resume.
func Credentials(ctx context.Context, directory string) error {
if err := safeio.ValidatePrivateDirectory(directory); err != nil {
return fmt.Errorf("use an existing private preparation directory")
}
if _, err := safeio.ReadCanonicalPrivateRegular(filepath.Join(directory, "thothii-installation.yaml"), 1<<20); err != nil {
return fmt.Errorf("prepare installation documents first")
}
secrets := filepath.Join(directory, "secrets")
if err := safeio.EnsurePrivateDirectory(secrets); err != nil {
return fmt.Errorf("secrets directory must be private and operator-owned")
}
for _, name := range []string{"catalog-runtime-password", "catalog-migrator-password", "admin-password"} {
path := filepath.Join(secrets, name)
if _, err := os.Lstat(path); err == nil {
value, err := safeio.ReadCanonicalPrivateRegular(path, 64<<10)
if err != nil || len(strings.TrimSpace(string(value))) < 32 {
return fmt.Errorf("existing technical credentials must be private, readable and at least 32 characters; no file was replaced")
}
continue
} else if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("cannot inspect technical credentials")
}
value := make([]byte, 32)
if _, err := rand.Read(value); err != nil {
return fmt.Errorf("cannot generate random credentials")
}
if err := safeio.WriteCanonicalNewPrivateFile(path, []byte(hex.EncodeToString(value)+"\n"), 0o600); err != nil {
return fmt.Errorf("cannot create credential; existing files are retained")
}
}
for name, contents := range map[string]string{"secrets.env": "# Supply the provider credential; never commit this file.\nOPENAI_API_KEY=CHANGE_ME\n", "pi-auth.json": "{}\n"} {
path := filepath.Join(secrets, name)
if _, err := os.Lstat(path); errors.Is(err, os.ErrNotExist) {
if err := safeio.WriteCanonicalNewPrivateFile(path, []byte(contents), 0o600); err != nil {
return fmt.Errorf("cannot create external credential template")
}
} else if _, err := safeio.ReadCanonicalPrivateRegular(path, 64<<10); err != nil {
return fmt.Errorf("existing credential template is not private or readable")
}
}
authDirectory := filepath.Join(directory, "auth")
if _, err := os.Lstat(filepath.Join(authDirectory, "auth.yaml")); err == nil {
if _, _, err := authconfig.Load(authDirectory); err != nil {
return fmt.Errorf("existing authentication documents are invalid; repair them explicitly")
}
return nil
}
installation := config.Installation{Authentication: config.Authentication{ConfigDirectory: authDirectory}}
if authconfig.Run(ctx, installation, []string{"configure", "--mode", "local", "--public-url", "http://localhost:8080", "--admin-user", "admin", "--password-file", filepath.Join(secrets, "admin-password")}, strings.NewReader(""), io.Discard, io.Discard) != 0 {
return fmt.Errorf("cannot prepare local administrator; inspect protected auth files and retry without replacing existing credentials")
}
return nil
}