Skip to content

Commit

Permalink
Add support for CSI as workspace type with Hashicorp/Vault example
Browse files Browse the repository at this point in the history
  • Loading branch information
JeromeJu committed Jul 11, 2022
1 parent da43d0e commit 6e76e55
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ Features currently in "alpha" are:
| [Matrix](./matrix.md) | [TEP-0090](https://github.com/tektoncd/community/blob/main/teps/0090-matrix.md) | | |
| [Embedded Statuses](pipelineruns.md#configuring-usage-of-taskrun-and-run-embedded-statuses) | [TEP-0100](https://github.com/tektoncd/community/blob/main/teps/0100-embedded-taskruns-and-runs-status-in-pipelineruns.md) | | |
| [Task-level Resource Requirements](compute-resources.md#task-level-compute-resources-configuration) | [TEP-0104](https://github.com/tektoncd/community/blob/main/teps/0104-tasklevel-resource-requirements.md) | | |
| [CSI Workspace Type](workspaces.md#csi) | | | |

## Configuring High Availability

Expand Down
36 changes: 36 additions & 0 deletions docs/workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,42 @@ workspaces:
secretName: my-secret
```

##### `csi`

This is an alpha feature. The `enable-api-fields` feature flag [must be set to `"alpha"`](./install.md)
for csi volume source to function.

The `csi` field references a [`csi` volume](https://kubernetes.io/docs/concepts/storage/volumes/#csi).
Using a `csi` volume has the following limitations:

- `csi` volume sources require a volume driver to use, which must correspond to the value by the CSI driver as defined in the [CSI spec](https://github.com/container-storage-interface/spec/blob/master/spec.md#getplugininfo).

```yaml
workspaces:
- name: my-credentials
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "vault-database"
```

Example of CSI workspace using Hashicorp Vault:

- Install the required csi driver. eg. [secrets-store-csi-driver](https://github.com/hashicorp/vault-csi-provider#using-yaml)
- Install the `vault` Provider onto the kubernetes cluster. [Reference](https://learn.hashicorp.com/tutorials/vault/kubernetes-raft-deployment-guide)
- Deploy a provider via [example](https://gist.github.com/JeromeJu/cc8e4e758029b6694806604750b8911c)
- Create a SecretProviderClass Provider using the following [yaml](https://github.com/tektoncd/pipeline/blob/main/examples/v1beta1/pipelineruns/no-ci/csi-workspace.yaml#L1-L19)
- Specify the ServiceAccount via vault:

```
vault write auth/kubernetes/role/database \
bound_service_account_names=default \
bound_service_account_namespaces=default \
policies=internal-app \
ttl=20m
```

If you need support for a `VolumeSource` type not listed above, [open an issue](https://github.com/tektoncd/pipeline/issues) or
a [pull request](https://github.com/tektoncd/pipeline/blob/main/CONTRIBUTING.md).

Expand Down
61 changes: 61 additions & 0 deletions examples/v1beta1/pipelineruns/no-ci/csi-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-database
spec:
provider: vault
secretObjects:
- data:
- key: password
objectName: db-password
secretName: dbpass
type: Opaque
parameters:
vaultAddress: "http://vault.default:8200"
roleName: "database"
objects: |
- objectName: "db-password"
secretPath: "secret/data/db-pass"
secretKey: "password"
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: csi-task
spec:
workspaces:
- name: secret-password
steps:
- name: fetch-csi
image: ubuntu
script: cat $(workspaces.secret-password.path)/db-password
---
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: csi-pipeline
spec:
workspaces:
- name: secret-vault
tasks:
- name: fetch-csi
taskRef:
name: csi-task
workspaces:
- name: secret-password
workspace: secret-vault
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: csi-credential-
spec:
pipelineRef:
name: csi-pipeline
workspaces:
- name: secret-vault
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "vault-database"
8 changes: 7 additions & 1 deletion pkg/apis/pipeline/v1beta1/openapi_generated.go

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

4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2852,6 +2852,10 @@
"description": "ConfigMap represents a configMap that should populate this workspace.",
"$ref": "#/definitions/v1.ConfigMapVolumeSource"
},
"csi": {
"description": "CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.",
"$ref": "#/definitions/v1.CSIVolumeSource"
},
"emptyDir": {
"description": "EmptyDir represents a temporary directory that shares a Task's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir Either this OR PersistentVolumeClaim can be used.",
"$ref": "#/definitions/v1.EmptyDirVolumeSource"
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/pipeline/v1beta1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type WorkspaceBinding struct {
// Secret represents a secret that should populate this workspace.
// +optional
Secret *corev1.SecretVolumeSource `json:"secret,omitempty"`
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.
// +optional
CSI *corev1.CSIVolumeSource `json:"csi,omitempty"`
}

// WorkspacePipelineDeclaration creates a named slot in a Pipeline that a PipelineRun
Expand Down
19 changes: 18 additions & 1 deletion pkg/apis/pipeline/v1beta1/workspace_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package v1beta1
import (
"context"

"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/version"
"k8s.io/apimachinery/pkg/api/equality"
"knative.dev/pkg/apis"
)
Expand All @@ -36,7 +38,7 @@ var allVolumeSourceFields = []string{
// Validate looks at the Volume provided in wb and makes sure that it is valid.
// This means that only one VolumeSource can be specified, and also that the
// supported VolumeSource is itself valid.
func (b *WorkspaceBinding) Validate(context.Context) *apis.FieldError {
func (b *WorkspaceBinding) Validate(ctx context.Context) (errs *apis.FieldError) {
if equality.Semantic.DeepEqual(b, &WorkspaceBinding{}) || b == nil {
return apis.ErrMissingField(apis.CurrentField)
}
Expand Down Expand Up @@ -66,6 +68,18 @@ func (b *WorkspaceBinding) Validate(context.Context) *apis.FieldError {
return apis.ErrMissingField("secret.secretName")
}

// The csi workspace is only supported when the alpha feature gate is enabled.
// For a CSI to work, you must provide and have installed the driver to use.
if b.CSI != nil {
errs := version.ValidateEnabledAPIFields(ctx, "csi workspace type", config.AlphaAPIFields).ViaField("workspaces")
if errs != nil {
return errs
}
if b.CSI.Driver == "" {
return apis.ErrMissingField("csi.driver")
}
}

return nil
}

Expand All @@ -88,5 +102,8 @@ func (b *WorkspaceBinding) numSources() int {
if b.Secret != nil {
n++
}
if b.CSI != nil {
n++
}
return n
}
68 changes: 53 additions & 15 deletions pkg/apis/pipeline/v1beta1/workspace_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package v1beta1
package v1beta1_test

import (
"context"
"testing"

"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -28,18 +30,19 @@ import (
func TestWorkspaceBindingValidateValid(t *testing.T) {
for _, tc := range []struct {
name string
binding *WorkspaceBinding
binding *v1beta1.WorkspaceBinding
wc func(context.Context) context.Context
}{{
name: "Valid PVC",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "pool-party",
},
},
}, {
name: "Valid volumeClaimTemplate",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
VolumeClaimTemplate: &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -57,13 +60,13 @@ func TestWorkspaceBindingValidateValid(t *testing.T) {
},
}, {
name: "Valid emptyDir",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}, {
name: "Valid configMap",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Expand All @@ -73,15 +76,28 @@ func TestWorkspaceBindingValidateValid(t *testing.T) {
},
}, {
name: "Valid secret",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
Secret: &corev1.SecretVolumeSource{
SecretName: "my-secret",
},
},
}, {
name: "Valid csi",
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
CSI: &corev1.CSIVolumeSource{
Driver: "my-csi",
},
},
wc: config.EnableAlphaAPIFields,
}} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.binding.Validate(context.Background()); err != nil {
ctx := context.Background()
if tc.wc != nil {
ctx = tc.wc(ctx)
}
if err := tc.binding.Validate(ctx); err != nil {
t.Errorf("didnt expect error for valid binding but got: %v", err)
}
})
Expand All @@ -92,13 +108,14 @@ func TestWorkspaceBindingValidateValid(t *testing.T) {
func TestWorkspaceBindingValidateInvalid(t *testing.T) {
for _, tc := range []struct {
name string
binding *WorkspaceBinding
binding *v1beta1.WorkspaceBinding
wc func(context.Context) context.Context
}{{
name: "no binding provided",
binding: nil,
}, {
name: "Provided both pvc and emptydir",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
EmptyDir: &corev1.EmptyDirVolumeSource{},
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
Expand All @@ -107,30 +124,51 @@ func TestWorkspaceBindingValidateInvalid(t *testing.T) {
},
}, {
name: "Provided neither pvc nor emptydir",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
},
}, {
name: "Provided pvc without claim name",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{},
},
}, {
name: "Provide configmap without a name",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
ConfigMap: &corev1.ConfigMapVolumeSource{},
},
}, {
name: "Provide secret without a secretName",
binding: &WorkspaceBinding{
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
Secret: &corev1.SecretVolumeSource{},
},
}, {
name: "csi workspace should be disallowed without alpha feature gate",
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
CSI: &corev1.CSIVolumeSource{
Driver: "csi-driver",
},
},
}, {
name: "Provide csi without a driver",
binding: &v1beta1.WorkspaceBinding{
Name: "beth",
CSI: &corev1.CSIVolumeSource{
Driver: "",
},
},
wc: config.EnableAlphaAPIFields,
}} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.binding.Validate(context.Background()); err == nil {
ctx := context.Background()
if tc.wc != nil {
ctx = tc.wc(ctx)
}
if err := tc.binding.Validate(ctx); err == nil {
t.Errorf("expected error for invalid binding but didn't get any!")
}
})
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go

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

3 changes: 3 additions & 0 deletions pkg/workspace/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ func CreateVolumes(wb []v1beta1.WorkspaceBinding) map[string]corev1.Volume {
case w.Secret != nil:
s := *w.Secret
v.setVolumeSource(w.Name, name, corev1.VolumeSource{Secret: &s})
case w.CSI != nil:
csi := *w.CSI
v.setVolumeSource(w.Name, name, corev1.VolumeSource{CSI: &csi})
}
}
return v
Expand Down
Loading

0 comments on commit 6e76e55

Please sign in to comment.