Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/v1alpha1/gitrepo_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type GitRepoTemplate struct {
// DeployKeys optional list of SSH deploy keys. If not set, not deploy keys will be configured
DeployKeys map[string]DeployKey `json:"deployKeys,omitempty"`
// Path to Git repository
GeneratedDeployKeys map[string]DeployKeyTemplate `json:"generatedDeployKeys,omitempty"`
// Path to Git repository
Path string `json:"path,omitempty"`
// RepoName name of Git repository
RepoName string `json:"repoName,omitempty"`
Expand Down Expand Up @@ -153,6 +155,14 @@ type DeployKey struct {
WriteAccess bool `json:"writeAccess,omitempty"`
}

// DeployKeyTemplate defines an SSH key to be generated for git operations.
type DeployKeyTemplate struct {
// Type defines what type the key is. For key generation, currently only `ssh-rsa` and `ssh-ed25519` are supported.
Type string `json:"type,omitempty"`
// WriteAccess if the key has RW access or not
WriteAccess bool `json:"writeAccess,omitempty"`
}

// GitRepoStatus defines the observed state of GitRepo
type GitRepoStatus struct {
// Updated by Operator with current phase. The GitPhase enum will be used for application logic
Expand Down
22 changes: 22 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions config/crd/bases/syn.tools_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,22 @@ spec:
displayName:
description: DisplayName of Git repository
type: string
generatedDeployKeys:
additionalProperties:
description: DeployKeyTemplate defines an SSH key to be generated
for git operations.
properties:
type:
description: Type defines what type the key is. For key
generation, currently only `ssh-rsa` and `ssh-ed25519`
are supported.
type: string
writeAccess:
description: WriteAccess if the key has RW access or not
type: boolean
type: object
description: Path to Git repository
type: object
path:
description: Path to Git repository
type: string
Expand Down
15 changes: 15 additions & 0 deletions config/crd/bases/syn.tools_gitrepos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@ spec:
displayName:
description: DisplayName of Git repository
type: string
generatedDeployKeys:
additionalProperties:
description: DeployKeyTemplate defines an SSH key to be generated
for git operations.
properties:
type:
description: Type defines what type the key is. For key generation,
currently only `ssh-rsa` and `ssh-ed25519` are supported.
type: string
writeAccess:
description: WriteAccess if the key has RW access or not
type: boolean
type: object
description: Path to Git repository
type: object
path:
description: Path to Git repository
type: string
Expand Down
33 changes: 33 additions & 0 deletions config/crd/bases/syn.tools_tenants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,23 @@ spec:
displayName:
description: DisplayName of Git repository
type: string
generatedDeployKeys:
additionalProperties:
description: DeployKeyTemplate defines an SSH key to be
generated for git operations.
properties:
type:
description: Type defines what type the key is. For
key generation, currently only `ssh-rsa` and `ssh-ed25519`
are supported.
type: string
writeAccess:
description: WriteAccess if the key has RW access or
not
type: boolean
type: object
description: Path to Git repository
type: object
path:
description: Path to Git repository
type: string
Expand Down Expand Up @@ -476,6 +493,22 @@ spec:
displayName:
description: DisplayName of Git repository
type: string
generatedDeployKeys:
additionalProperties:
description: DeployKeyTemplate defines an SSH key to be generated
for git operations.
properties:
type:
description: Type defines what type the key is. For key
generation, currently only `ssh-rsa` and `ssh-ed25519`
are supported.
type: string
writeAccess:
description: WriteAccess if the key has RW access or not
type: boolean
type: object
description: Path to Git repository
type: object
path:
description: Path to Git repository
type: string
Expand Down
33 changes: 33 additions & 0 deletions config/crd/bases/syn.tools_tenanttemplates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,23 @@ spec:
displayName:
description: DisplayName of Git repository
type: string
generatedDeployKeys:
additionalProperties:
description: DeployKeyTemplate defines an SSH key to be
generated for git operations.
properties:
type:
description: Type defines what type the key is. For
key generation, currently only `ssh-rsa` and `ssh-ed25519`
are supported.
type: string
writeAccess:
description: WriteAccess if the key has RW access or
not
type: boolean
type: object
description: Path to Git repository
type: object
path:
description: Path to Git repository
type: string
Expand Down Expand Up @@ -476,6 +493,22 @@ spec:
displayName:
description: DisplayName of Git repository
type: string
generatedDeployKeys:
additionalProperties:
description: DeployKeyTemplate defines an SSH key to be generated
for git operations.
properties:
type:
description: Type defines what type the key is. For key
generation, currently only `ssh-rsa` and `ssh-ed25519`
are supported.
type: string
writeAccess:
description: WriteAccess if the key has RW access or not
type: boolean
type: object
description: Path to Git repository
type: object
path:
description: Path to Git repository
type: string
Expand Down
80 changes: 80 additions & 0 deletions controllers/gitrepo/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/charmbracelet/keygen"
"github.com/go-logr/logr"
synv1alpha1 "github.com/projectsyn/lieutenant-operator/api/v1alpha1"
"go.uber.org/multierr"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"
Expand All @@ -25,6 +28,11 @@ import (
"github.com/projectsyn/lieutenant-operator/pipeline"
)

const DEPLOY_KEY_NAME_INFIX = "-deploy-key-"
const DEPLOY_KEY_SECRET_PUBKEY = "publicKey"
const DEPLOY_KEY_SECRET_PRIVKEY = "privateKey"
const DEPLOY_KEY_SECRET_TYPE = "type"

func Steps(obj pipeline.Object, data *pipeline.Context) pipeline.Result {
return steps(obj, data, manager.GetGitClient)
}
Expand Down Expand Up @@ -101,6 +109,10 @@ func steps(obj pipeline.Object, data *pipeline.Context, getGitClient gitClientFa
return pipeline.Result{Err: handleRepoError(data.Context, fmt.Errorf("ensure ci variables: %w", err), instance, data.Client)}
}

if err := ensureGeneratedDeployKeys(data.Context, data.Client, instance); err != nil {
return pipeline.Result{Err: handleRepoError(data.Context, fmt.Errorf("ensure ci variables: %w", err), instance, data.Client)}
}

err = repo.CommitTemplateFiles()
if err != nil {
return pipeline.Result{Err: handleRepoError(data.Context, err, instance, data.Client)}
Expand Down Expand Up @@ -252,6 +264,74 @@ func ensureCIVariables(ctx context.Context, cli client.Client, instance *synv1al
return nil
}

// ensureGeneratedDeployKeys ensures that the repo's `generateDeployKey` entries
// all have a corresponding `deployKey` entry, generating SSH keys as required
// and storing them in individual secrets.
func ensureGeneratedDeployKeys(ctx context.Context, cli client.Client, instance *synv1alpha1.GitRepo) error {
for genKey, settings := range instance.Spec.GeneratedDeployKeys {
secretName := instance.Name + DEPLOY_KEY_NAME_INFIX + genKey
secretNSName := types.NamespacedName{Name: secretName, Namespace: instance.Namespace}
secret := &corev1.Secret{}

err := cli.Get(ctx, secretNSName, secret)
if err != nil {
if !apierrors.IsNotFound(err) {
return err
}
err = generateNewDeployKeySecret(ctx, cli, settings, secretNSName, secret)
if err != nil {
return err
}
}

pubkeyB, ok := secret.Data[DEPLOY_KEY_SECRET_PUBKEY]
if !ok {
return fmt.Errorf("could not retrieve deploy key from secret: missing key: %s", DEPLOY_KEY_SECRET_PUBKEY)
}
pubkey := string(pubkeyB[:])
Comment thread
HappyTetrahedron marked this conversation as resolved.
Outdated
parts := strings.Split(pubkey, " ")

oldKey, ok := instance.Spec.DeployKeys[genKey]
if !ok || oldKey.Key != parts[1] {
if instance.Spec.DeployKeys == nil {
instance.Spec.DeployKeys = make(map[string]synv1alpha1.DeployKey)
}
instance.Spec.DeployKeys[genKey] = synv1alpha1.DeployKey{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer tracking the generated deploy keys separately from externally provisioned deploy keys (I'd probably put the generated keys in .status.deployKeys and explicitly reference the associated secrets there).

Afaict, with the current approach, users could create chaos by requesting a generated deploy key named steward (if I read the code correctly, the generated key would win over the key supplied by steward, permanently breaking ArgoCD on such clusters).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.
Naming conflicts are still an issue - Gitlab can in theory have multiple deploy keys with the same name, but our whole update and deletion logic relies on matching keys by name so that'll fall apart. So I decided to add a prefix to all generated keys. There's still nothing stopping the user from creating a naming conflict ... but it's less likely to happen by accident like this.

Type: parts[0],
Key: parts[1],
WriteAccess: settings.WriteAccess,
}
}
}

return nil
}

func generateNewDeployKeySecret(ctx context.Context, cli client.Client, settings synv1alpha1.DeployKeyTemplate, secretName types.NamespacedName, secretRef *corev1.Secret) error {
Comment thread
HappyTetrahedron marked this conversation as resolved.
Outdated
keyType := keygen.Ed25519
if settings.Type == "ssh-rsa" {
keyType = keygen.RSA
}

kp, err := keygen.New(
"/tmp/"+secretName.Name,
keygen.WithKeyType(keyType),
)
if err != nil {
return err
}

secretRef.Name = secretName.Name
secretRef.Namespace = secretName.Namespace

secretRef.Data = make(map[string][]byte)

secretRef.Data[DEPLOY_KEY_SECRET_PUBKEY] = kp.RawAuthorizedKey()
secretRef.Data[DEPLOY_KEY_SECRET_PRIVKEY] = kp.RawPrivateKey()

return cli.Create(ctx, secretRef)
}

// valueFromEnvVar returns the value of an envVar. It returns an error if the envVar is invalid or the value cannot be retrieved.
// EnvVars with both value and valueFrom are invalid.
// An envVar with no value and no valueFrom returns an empty string.
Expand Down
Loading