feat(server): activate projected authentication safely
This commit is contained in:
@@ -39,7 +39,14 @@ type descriptor struct {
|
||||
}
|
||||
|
||||
type authenticationDescriptor struct {
|
||||
ConfigDirectory string `yaml:"configDirectory"`
|
||||
ConfigDirectory string `yaml:"configDirectory"`
|
||||
RuntimeProjection *runtimeProjectionDescriptor `yaml:"runtimeProjection"`
|
||||
}
|
||||
|
||||
type runtimeProjectionDescriptor struct {
|
||||
Directory string `yaml:"directory"`
|
||||
UID uint32 `yaml:"uid"`
|
||||
GID uint32 `yaml:"gid"`
|
||||
}
|
||||
|
||||
type workspaceRepositoryDescriptor struct {
|
||||
@@ -55,9 +62,17 @@ type WorkspaceRepository struct {
|
||||
Access string
|
||||
}
|
||||
|
||||
// RuntimeProjection is the non-secret runtime root and numeric container ownership contract.
|
||||
type RuntimeProjection struct {
|
||||
Directory string
|
||||
UID uint32
|
||||
GID uint32
|
||||
}
|
||||
|
||||
// Authentication is the non-secret filesystem location for the installation auth configuration.
|
||||
type Authentication struct {
|
||||
ConfigDirectory string
|
||||
ConfigDirectory string
|
||||
RuntimeProjection *RuntimeProjection
|
||||
}
|
||||
|
||||
// Installation is a validated local Compose installation. It intentionally contains paths, not
|
||||
@@ -114,6 +129,14 @@ func Load(path string) (Installation, error) {
|
||||
return Installation{}, errors.New("authentication.configDirectory must be an absolute canonical path")
|
||||
}
|
||||
|
||||
authentication := Authentication{ConfigDirectory: raw.Authentication.ConfigDirectory}
|
||||
if raw.Authentication.RuntimeProjection != nil {
|
||||
authentication.RuntimeProjection = &RuntimeProjection{
|
||||
Directory: raw.Authentication.RuntimeProjection.Directory,
|
||||
UID: raw.Authentication.RuntimeProjection.UID,
|
||||
GID: raw.Authentication.RuntimeProjection.GID,
|
||||
}
|
||||
}
|
||||
installation := Installation{
|
||||
Path: path,
|
||||
Profile: raw.Profile,
|
||||
@@ -124,7 +147,7 @@ func Load(path string) (Installation, error) {
|
||||
Branch: raw.WorkspaceRepository.Branch,
|
||||
Access: raw.WorkspaceRepository.Access,
|
||||
},
|
||||
Authentication: Authentication{ConfigDirectory: raw.Authentication.ConfigDirectory},
|
||||
Authentication: authentication,
|
||||
Overrides: make([]string, 0, len(raw.Overrides)),
|
||||
}
|
||||
values, err := installation.environmentValues()
|
||||
@@ -140,6 +163,14 @@ func Load(path string) (Installation, error) {
|
||||
}
|
||||
installation.Overrides = append(installation.Overrides, filepath.Clean(override))
|
||||
}
|
||||
if err := installation.validateRuntimeAuthProjection(values); err != nil {
|
||||
return Installation{}, err
|
||||
}
|
||||
if installation.HasRuntimeAuthProjection() {
|
||||
if err := requireRegularFile(installation.runtimeAuthProjectionComposePath(), "runtime authentication Compose override"); err != nil {
|
||||
return Installation{}, err
|
||||
}
|
||||
}
|
||||
for _, composeFile := range installation.ComposeFiles()[:2] {
|
||||
if err := requireRegularFile(composeFile, "Compose file"); err != nil {
|
||||
return Installation{}, err
|
||||
@@ -161,6 +192,49 @@ func Load(path string) (Installation, error) {
|
||||
// AuthenticationDirectory returns the descriptor-owned, non-secret authentication root.
|
||||
func (i Installation) AuthenticationDirectory() string { return i.Authentication.ConfigDirectory }
|
||||
|
||||
// RuntimeAuthProjection returns an independent descriptor copy when this installation uses the
|
||||
// Linux-only runtime auth publication contract.
|
||||
func (i Installation) RuntimeAuthProjection() *RuntimeProjection {
|
||||
if i.Authentication.RuntimeProjection == nil {
|
||||
return nil
|
||||
}
|
||||
projection := *i.Authentication.RuntimeProjection
|
||||
return &projection
|
||||
}
|
||||
|
||||
// HasRuntimeAuthProjection reports whether the descriptor selects the runtime auth projection.
|
||||
func (i Installation) HasRuntimeAuthProjection() bool {
|
||||
return i.Authentication.RuntimeProjection != nil
|
||||
}
|
||||
|
||||
func (i Installation) validateRuntimeAuthProjection(values map[string]string) error {
|
||||
projection := i.RuntimeAuthProjection()
|
||||
if projection == nil {
|
||||
if values["THT_AUTH_RUNTIME_ROOT"] != "" {
|
||||
return errors.New("THT_AUTH_RUNTIME_ROOT requires authentication.runtimeProjection")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if i.Profile != "server" {
|
||||
return errors.New("authentication.runtimeProjection requires the server profile")
|
||||
}
|
||||
if err := safeio.ValidateCanonicalPath(projection.Directory); err != nil || projection.Directory == i.AuthenticationDirectory() {
|
||||
return errors.New("authentication.runtimeProjection.directory must be a distinct absolute canonical path")
|
||||
}
|
||||
if projection.UID != 10001 || projection.GID != 10001 {
|
||||
return errors.New("authentication.runtimeProjection requires uid and gid 10001")
|
||||
}
|
||||
if values["THT_AUTH_RUNTIME_ROOT"] != projection.Directory {
|
||||
return errors.New("authentication.runtimeProjection.directory must match THT_AUTH_RUNTIME_ROOT")
|
||||
}
|
||||
for _, override := range i.Overrides {
|
||||
if filepath.Clean(override) == i.runtimeAuthProjectionComposePath() {
|
||||
return errors.New("runtime authentication Compose override is automatic and must not be declared")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var safeGitBranch = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]*$`)
|
||||
var scpSSHRemote = regexp.MustCompile(`^git@[^:/\s]+:[^\s]+$`)
|
||||
|
||||
@@ -250,6 +324,9 @@ func (i Installation) ComposeFiles() []string {
|
||||
filepath.Join(i.ProjectDirectory, "deploy", "compose."+i.Profile+".yaml"),
|
||||
}
|
||||
files = append(files, i.Overrides...)
|
||||
if i.HasRuntimeAuthProjection() {
|
||||
files = append(files, i.runtimeAuthProjectionComposePath())
|
||||
}
|
||||
currentImage := i.CurrentImageOverridePath()
|
||||
if info, err := os.Lstat(currentImage); err == nil && info.Mode().IsRegular() {
|
||||
files = append(files, currentImage)
|
||||
@@ -257,6 +334,10 @@ func (i Installation) ComposeFiles() []string {
|
||||
return files
|
||||
}
|
||||
|
||||
func (i Installation) runtimeAuthProjectionComposePath() string {
|
||||
return filepath.Join(i.ProjectDirectory, "deploy", "compose.auth-runtime-projection.yaml")
|
||||
}
|
||||
|
||||
// ControlDirectory contains state that is private to one installation descriptor, even when
|
||||
// multiple installations intentionally share one source checkout.
|
||||
func (i Installation) ControlDirectory() string {
|
||||
|
||||
Reference in New Issue
Block a user