Skip to content

Commit

Permalink
Add GitCredentialSecret to checkout params
Browse files Browse the repository at this point in the history
  • Loading branch information
DrJosh9000 committed Aug 19, 2024
1 parent 17ae5f3 commit dc4a415
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 10 deletions.
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Options](#Options)
- [Sample Buildkite Pipeline](#Sample-Buildkite-Pipelines)
- [Cloning repos via SSH](#Cloning-repos-via-SSH)
- [Cloning repos via HTTPS](#Cloning-repos-via-HTTPS)
- [Pod Spec Patch](#Pod-Spec-Patch)
- [Sidecars](#Sidecars)
- [Extra volume mounts](#Extra-volume-mounts)
Expand Down Expand Up @@ -267,6 +268,62 @@ steps:
- --image=ttl.sh/example:1h
```

### Cloning repos via HTTPS

To use HTTPS to clone private repos, you can use a `.git-credentials` file stored in a secret, and refer to this secret using the `gitCredentialsSecret` checkout parameter.

By default, this secret is only attached to the `checkout` container, and will not necessarily be available in your job containers.
If you need the `.git-credentials` file inside the other containers as well, you can add a volume mount (of type secret) for the `git-credentials` volume, as needed.

#### Example secret creation for HTTPS cloning
Once again, this example is illustrative only.

First, create a Kubernetes secret containing the key `.git-credentials`, formatted in the manner expected by [the `store` Git credendial helper](https://git-scm.com/docs/git-credential-store):
```bash
kubectl create secret generic my-git-credentials --from-file='.git-credentials'="$HOME/.git-credentials"
```

Then you can use the `checkout/gitCredentialsSecret` (in your pipeline) or `default-checkout-params/gitCredentialsSecret` (in values.yaml) to reference the secret volume source:

```yaml
# pipeline.yaml
steps:
- label: build image
agents:
queue: kubernetes
plugins:
- kubernetes:
checkout:
gitCredentialsSecret:
secretName: my-git-credentials # <----
podSpec:
...
```

```yaml
# values.yaml
...
default-checkout-params:
gitCredentialsSecret:
secretName: my-git-credentials
...
```

If you wish to use a different key within the secret than `.git-credentials`, you can
[project it](https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#project-secret-keys-to-specific-file-paths)
to `.git-credentials` by using `items` within `gitCredentialsSecret`.

```yaml
# values.yaml
...
default-checkout-params:
gitCredentialsSecret:
secretName: my-git-credentials
items:
- key: funky-creds
path: .git-credentials
...
```

### Pod Spec Patch
Rather than defining the entire Pod Spec in a step, there is the option to define a [strategic merge patch](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/) in the controller.
Expand Down
3 changes: 3 additions & 0 deletions charts/agent-stack-k8s/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@
"default": null,
"title": "If set, appends the BUILDKITE_GIT_FETCH_FLAGS variable"
},
"gitCredentialsSecret": {
"$ref": "https://kubernetesjsonschema.dev/master/_definitions.json#/definitions/io.k8s.api.core.v1.SecretVolumeSource"
},
"envFrom": {
"type": "array",
"default": [],
Expand Down
3 changes: 3 additions & 0 deletions cmd/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ func TestReadAndParseConfig(t *testing.T) {
}},
},
DefaultCheckoutParams: &config.CheckoutParams{
GitCredentialsSecret: &corev1.SecretVolumeSource{
SecretName: "my-git-credentials",
},
EnvFrom: []corev1.EnvFromSource{{
Prefix: "GITHUB_",
SecretRef: &corev1.SecretEnvSource{
Expand Down
2 changes: 2 additions & 0 deletions examples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ prohibit-kubernetes-plugin: true

# Applies to the checkout container in all spawned pods
default-checkout-params:
gitCredentialsSecret:
secretName: "my-git-credentials"
envFrom:
- prefix: GITHUB_
secretRef:
Expand Down
16 changes: 12 additions & 4 deletions internal/controller/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ func (sc *SidecarParams) ApplyTo(ctr *corev1.Container) {
// CheckoutParams contains parameters that provide additional control over the
// checkout container.
type CheckoutParams struct {
Skip *bool `json:"skip,omitempty"`
CloneFlags *string `json:"cloneFlags,omitempty"`
FetchFlags *string `json:"fetchFlags,omitempty"`
EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"`
Skip *bool `json:"skip,omitempty"`
CloneFlags *string `json:"cloneFlags,omitempty"`
FetchFlags *string `json:"fetchFlags,omitempty"`
GitCredentialsSecret *corev1.SecretVolumeSource `json:"gitCredentialsSecret,omitempty"`
EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"`
}

func (co *CheckoutParams) ApplyTo(ctr *corev1.Container) {
Expand All @@ -142,3 +143,10 @@ func (co *CheckoutParams) ApplyTo(ctr *corev1.Container) {
}
ctr.EnvFrom = append(ctr.EnvFrom, co.EnvFrom...)
}

func (co *CheckoutParams) GitCredsSecret() *corev1.SecretVolumeSource {
if co == nil {
return nil
}
return co.GitCredentialsSecret
}
46 changes: 40 additions & 6 deletions internal/controller/scheduler/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,36 @@ func (w *worker) createCheckoutContainer(
}
}

// If configured, set up a volume mount of a secret containing a
// .git-credentials file. k8sPlugin (if allowed) supersedes the default.
gitCredsSecret := w.cfg.DefaultCheckoutParams.GitCredsSecret()
if k8sPlugin != nil {
gitCredsSecret = k8sPlugin.CheckoutParams.GitCredsSecret()
}
gitConfigCmd := "true"
if gitCredsSecret != nil {
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{
Name: "git-credentials",
VolumeSource: corev1.VolumeSource{Secret: gitCredsSecret},
})

checkoutContainer.VolumeMounts = append(checkoutContainer.VolumeMounts, corev1.VolumeMount{
Name: "git-credentials",
ReadOnly: true,
MountPath: "/tmp/ro/.git-credentials",
SubPath: ".git-credentials",
})

// Why copy the file instead of mounting it directly into place?
// K8s secret mounts are always read only, but the 'store' credential
// helper always tries to write back to the file.
// If we didn't do this, we'd get some alarming-looking log lines like:
// "fatal: unable to write credential store: Resource busy"
// (which is Git being a drama llama - it doesn't impact the checkout
// process in any meaningful way).
gitConfigCmd = "cp /tmp/ro/.git-credentials ~ && git config --global credential.helper store"
}

// Ensure that the checkout occurs as the user/group specified in the pod's security context.
// we will create a buildkite-agent user/group in the checkout container as needed and switch
// to it. The created user/group will have the uid/gid specified in the pod's security context.
Expand All @@ -620,16 +650,17 @@ func (w *worker) createCheckoutContainer(
checkoutContainer.SecurityContext = &corev1.SecurityContext{
RunAsUser: ptr.To[int64](0),
RunAsGroup: ptr.To[int64](0),
RunAsNonRoot: ptr.To[bool](false),
RunAsNonRoot: ptr.To(false),
}

checkoutContainer.Command = []string{"ash", "-c"}
checkoutContainer.Args = []string{fmt.Sprintf(`set -exufo pipefail
addgroup -g %d buildkite-agent
adduser -D -u %d -G buildkite-agent -h /workspace buildkite-agent
su buildkite-agent -c "buildkite-agent-entrypoint bootstrap"`,
su buildkite-agent -c "%s && buildkite-agent-entrypoint bootstrap"`,
podGroup,
podUser,
gitConfigCmd,
)}

case podUser != 0 && podGroup == 0:
Expand All @@ -643,18 +674,21 @@ su buildkite-agent -c "buildkite-agent-entrypoint bootstrap"`,
checkoutContainer.Command = []string{"ash", "-c"}
checkoutContainer.Args = []string{fmt.Sprintf(`set -exufo pipefail
adduser -D -u %d -G root -h /workspace buildkite-agent
su buildkite-agent -c "buildkite-agent-entrypoint bootstrap"`,
su buildkite-agent -c "%s && buildkite-agent-entrypoint bootstrap"`,
podUser,
gitConfigCmd,
)}

// If the group is not root, but the user is root, I don't think we NEED to do anything. It's fine
// for the user and group to be root for the checked out repo, even though the Pod's security
// context has a non-root group.
default:
checkoutContainer.SecurityContext = nil
// these are the default, but that default is sepciifed in the agent repo, so lets make it explicit
checkoutContainer.Command = []string{"buildkite-agent-entrypoint"}
checkoutContainer.Args = []string{"bootstrap"}
checkoutContainer.Command = []string{"ash", "-c"}
checkoutContainer.Args = []string{fmt.Sprintf(`set -exufo pipefail
%s
buildkite-agent-entrypoint bootstrap`,
gitConfigCmd)}
}

return checkoutContainer
Expand Down

0 comments on commit dc4a415

Please sign in to comment.