orchard/internal/command/controller/bootstrap.go

116 lines
3.8 KiB
Go

package controller
import (
"crypto/tls"
"fmt"
"os"
"strings"
"github.com/cirruslabs/orchard/internal/certificatefingerprint"
"github.com/cirruslabs/orchard/internal/controller"
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
"github.com/pterm/pterm"
"github.com/samber/lo"
"github.com/sethvargo/go-password/password"
)
const BootstrapContextName = "bootstrap-context"
const BootstrapAdminName = "bootstrap-admin"
func Bootstrap(controllerInstance *controller.Controller, controllerCert tls.Certificate) (string, string, error) {
// Determine if we need to do anything at all
orchardBootstrapAdminToken, orchardBootstrapAdminTokenPresent := os.LookupEnv("ORCHARD_BOOTSTRAP_ADMIN_TOKEN")
serviceAccounts, err := controllerInstance.ServiceAccounts()
if err != nil {
return "", "", fmt.Errorf("failed to retrieve the number of service accounts: %w", err)
}
if len(serviceAccounts) != 0 && !orchardBootstrapAdminTokenPresent {
// No bootstrap is needed because there are service accounts
// present in the database (so it's not the first start)
// and no bootstrap admin token change is requested
//
// However, if the BootstrapAdminName service account still exists,
// return its credentials. We'll use them for updating the
// BootstrapContextName context.
if serviceAccount, ok := lo.Find(serviceAccounts, func(serviceAccount v1.ServiceAccount) bool {
return serviceAccount.Name == BootstrapAdminName
}); ok {
return serviceAccount.Name, serviceAccount.Token, nil
}
return "", "", nil
}
// Generate a bootstrap admin token if not present in the environment variable
if !orchardBootstrapAdminTokenPresent {
passwordGenerator, err := password.NewGenerator(&password.GeneratorInput{
LowerLetters: password.LowerLetters,
UpperLetters: password.UpperLetters,
Digits: password.Digits,
Symbols: strings.Map(func(r rune) rune {
// Avoid generating $ and " symbols
// as they cause issues in shell
switch r {
case '$', '"':
return -1
default:
return r
}
}, password.Symbols),
})
if err != nil {
return "", "", fmt.Errorf("failed to generate bootstrap admin token: "+
"failed to initialize password generator: %w", err)
}
orchardBootstrapAdminToken, err = passwordGenerator.Generate(32, 10, 10,
false, false)
if err != nil {
return "", "", fmt.Errorf("failed to generate bootstrap admin token: %w", err)
}
}
// Ensure bootstrap admin service account exists
if err := controllerInstance.EnsureServiceAccount(&v1.ServiceAccount{
Meta: v1.Meta{
Name: BootstrapAdminName,
},
Token: orchardBootstrapAdminToken,
Roles: v1.AllServiceAccountRoles(),
}); err != nil {
return "", "", err
}
// Report bootstrapping result to the user
var reasonToDisplay string
var serviceAccountTokenToDisplay string
if orchardBootstrapAdminTokenPresent {
reasonToDisplay = fmt.Sprintf("Re-created the %q service account using the token "+
"provided in the ORCHARD_BOOTSTRAP_ADMIN_TOKEN environment variable", BootstrapAdminName)
serviceAccountTokenToDisplay = "<hidden>"
} else {
reasonToDisplay = fmt.Sprintf("No service accounts found, created a new %q service "+
"account using a randomly-generated password", BootstrapAdminName)
serviceAccountTokenToDisplay = orchardBootstrapAdminToken
}
messages := []any{
pterm.Sprintf("%s:\n", reasonToDisplay),
pterm.Sprintln(),
pterm.Sprintf("Service account name: %s\n", pterm.Bold.Sprint(BootstrapAdminName)),
pterm.Sprintf("Service account token: %s\n", pterm.Bold.Sprint(serviceAccountTokenToDisplay)),
}
if !noTLS {
messages = append(messages, pterm.Sprintf("Certificate SHA-256 fingerprint: %s.\n",
pterm.Bold.Sprint(certificatefingerprint.CertificateFingerprint(controllerCert.Certificate[0]))))
}
pterm.Info.Print(messages...)
return BootstrapAdminName, orchardBootstrapAdminToken, nil
}