Skip to content

Commit

Permalink
Enable Fulcio e2e testing. (#219)
Browse files Browse the repository at this point in the history
There are several parts to this change:
1. Implement a new `ephemeralca` that just generates an in-memory certificate,
1. Rename `pkg/ca/pkcs11ca` to `pkg/ca/x509ca` since it had nothing `PKCS11` specific (shared with `ephemeralca` logic),
1. Add support for Kubernetes OIDC via Service Account Projected Volumes,
1. Have the KinD e2e test use `ephemeralca` and `cosign sign` an image.

I can split some of these pieces apart, but wanted to get this all working end-to-end, since a key goal was enabling e2e testing on KinD.

This follows a lot of the ideas from: https://github.com/mattmoor/kind-oidc

Related: #212
Fixes: #194

Signed-off-by: Matt Moore <mattomata@gmail.com>
  • Loading branch information
mattmoor authored Oct 28, 2021
1 parent 493a37e commit 58ec2fa
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 129 deletions.
69 changes: 69 additions & 0 deletions .github/workflows/verify-k8s.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,23 @@ jobs:
env:
CGO_ENABLED: 1
run: |
# Reduce the resource requests of Fulcio
sed -i -e 's,memory: "1G",memory: "100m",g' ${{ github.workspace }}/config/deployment.yaml
sed -i -e 's,cpu: ".5",memory: "50m",g' ${{ github.workspace }}/config/deployment.yaml
# Switch to the ephemeralca for testing.
sed -i -e 's,--ca=googleca,--ca=ephemeralca,g' ${{ github.workspace }}/config/deployment.yaml
# Drop the ct-log flag's value to elide CT-log uploads.
sed -i -E 's,"--ct-log-url=[^"]+","--ct-log-url=",g' ${{ github.workspace }}/config/deployment.yaml
# From: https://banzaicloud.com/blog/kubernetes-oidc/
# To be able to fetch the public keys and validate the JWT tokens against
# the Kubernetes cluster’s issuer we have to allow external unauthenticated
# requests. To do this, we bind this special role with a ClusterRoleBinding
# to unauthenticated users (make sure that this is safe in your environment,
# but only public keys are visible on this URL)
kubectl create clusterrolebinding oidc-reviewer \
--clusterrole=system:service-account-issuer-discovery \
--group=system:unauthenticated
kubectl create ns fulcio-dev
Expand All @@ -132,6 +147,60 @@ jobs:
kubectl get po -n fulcio-dev
- name: Run signing job
env:
CGO_ENABLED: 1
run: |
DIGEST=$(ko publish .)
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: check-oidc
spec:
template:
spec:
restartPolicy: Never
automountServiceAccountToken: false
containers:
- name: check-oidc
# This is a version of the cosign image published from CI with https://github.com/sigstore/cosign/pull/955
# TODO(mattmoor): Switch this to a release build after 1.3
image: gcr.io/projectsigstore/cosign/ci/cosign:46e274094551d5b29bd89eaa6499c0f39e60db2f
command: [/busybox/sh, -c]
args:
- |
/ko-app/cosign sign \
`# Target our cluster's Fulcio` \
--fulcio-url http://fulcio-server.fulcio-dev.svc \
`# Pass in the KinD OIDC token` \
--identity-token \$(cat /var/run/kind-oidc/token) \
`# Skip verification of the SCT since we've disabled that above.` \
--insecure-skip-verify=true \
`# Skip upload because we can't avoid Rekor on that path.` \
--upload=false \
${DIGEST}
env:
- name: COSIGN_EXPERIMENTAL
value: "true"
volumeMounts:
- name: oidc-info
mountPath: /var/run/kind-oidc
volumes:
- name: oidc-info
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 600 # Use as short-lived as possible.
audience: sigstore
EOF
kubectl wait --for=condition=Complete --timeout=90s job/check-oidc
- name: Collect logs
if: ${{ always() }}
run: |
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func Execute() {

func init() {
rootCmd.PersistentFlags().StringVar(&logType, "log_type", "dev", "logger type to use (dev/prod)")
rootCmd.PersistentFlags().String("ca", "", "googleca | pkcs11ca")
rootCmd.PersistentFlags().String("ca", "", "googleca | pkcs11ca | ephemeralca (for testing)")
rootCmd.PersistentFlags().String("aws-hsm-root-ca-path", "", "Path to root CA on disk (only used with AWS HSM)")
rootCmd.PersistentFlags().String("gcp_private_ca_parent", "", "private ca parent: /projects/<project>/locations/<location>/<name> (only used with --ca googleca)")
rootCmd.PersistentFlags().String("hsm-caroot-id", "", "HSM ID for Root CA (only used with --ca pkcs11ca)")
Expand Down
25 changes: 17 additions & 8 deletions cmd/app/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/sigstore/fulcio/pkg/ca/ephemeralca"
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/fulcio/pkg/generated/restapi"
"github.com/sigstore/fulcio/pkg/generated/restapi/operations"
Expand All @@ -37,16 +38,24 @@ var serveCmd = &cobra.Command{
Long: `Starts a http server and serves the configured api`,
Run: func(cmd *cobra.Command, args []string) {

if viper.GetString("ca") != "pkcs11ca" && viper.GetString("ca") != "googleca" {
log.Logger.Fatal("unknown CA: ", viper.GetString("ca"))
}
switch viper.GetString("ca") {
case "pkcs11ca":
if !viper.IsSet("hsm-caroot-id") {
log.Logger.Fatal("hsm-caroot-id must be set when using pkcs11ca")
}

if viper.GetString("ca") == "googleca" && !viper.IsSet("gcp_private_ca_parent") {
panic("gcp_private_ca_parent must be set when using googleca")
}
case "googleca":
if !viper.IsSet("gcp_private_ca_parent") {
log.Logger.Fatal("gcp_private_ca_parent must be set when using googleca")
}

case "ephemeralca":
if err := ephemeralca.Initialize(cmd.Context()); err != nil {
log.Logger.Fatalw("error initializing ephemeral CA", err)
}

if viper.GetString("ca") == "pkcs11ca" && !viper.IsSet("hsm-caroot-id") {
panic("hsm-caroot-id must be set when using pkcs11ca")
default:
log.Logger.Fatal("unknown CA: ", viper.GetString("ca"))
}

// Setup the logger to dev/prod
Expand Down
12 changes: 11 additions & 1 deletion config/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ spec:
volumeMounts:
- name: fulcio-config
mountPath: /etc/fulcio-config
- name: oidc-info
mountPath: /var/run/fulcio
resources:
requests:
memory: "1G"
Expand All @@ -61,7 +63,15 @@ spec:
- name: fulcio-config
configMap:
name: fulcio-config

- name: oidc-info
projected:
sources:
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
mode: 0666
---
apiVersion: v1
kind: Service
Expand Down
5 changes: 5 additions & 0 deletions config/fulcio-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ data:
"ClientID": "sigstore",
"Type": "email"
},
"https://kubernetes.default.svc": {
"IssuerURL": "https://kubernetes.default.svc",
"ClientID": "sigstore",
"Type": "kubernetes"
},
"https://oauth2.sigstore.dev/auth": {
"IssuerURL": "https://oauth2.sigstore.dev/auth",
"ClientID": "sigstore",
Expand Down
18 changes: 18 additions & 0 deletions federation/kubernetes.default.svc/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2021 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

url: https://kubernetes.default.svc
contact: mattmoor@chainguard.dev
description: "service account projected volumes"
type: "kubernetes"
4 changes: 4 additions & 0 deletions pkg/api/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ func SigningCertHandler(params operations.SigningCertParams, principal *oidc.IDT
PemCertificate, PemCertificateChain, err = GoogleCASigningCertHandler(ctx, subj, publicKeyPEM)
case "pkcs11ca":
PemCertificate, PemCertificateChain, err = Pkcs11CASigningCertHandler(ctx, subj, publicKey)
case "ephemeralca":
PemCertificate, PemCertificateChain, err = EphemeralCASigningCertHandler(ctx, subj, publicKey)
default:
return handleFulcioAPIError(params, http.StatusInternalServerError, err, genericCAError)
}
Expand Down Expand Up @@ -110,6 +112,8 @@ func Subject(ctx context.Context, tok *oidc.IDToken, cfg config.FulcioConfig, pu
return challenges.Spiffe(ctx, tok, publicKey, challenge)
case config.IssuerTypeGithubWorkflow:
return challenges.GithubWorkflow(ctx, tok, publicKey, challenge)
case config.IssuerTypeKubernetes:
return challenges.Kubernetes(ctx, tok, publicKey, challenge)
default:
return nil, fmt.Errorf("unsupported issuer: %s", iss.Type)
}
Expand Down
48 changes: 48 additions & 0 deletions pkg/api/ephemeralca_signing_cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package api

import (
"context"
"crypto/x509"
"encoding/pem"

"github.com/sigstore/fulcio/pkg/ca/ephemeralca"
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/challenges"
)

func EphemeralCASigningCertHandler(ctx context.Context, subj *challenges.ChallengeResult, publicKey []byte) (string, []string, error) {
rootCA, privKey := ephemeralca.CA()

pkixPubKey, err := x509.ParsePKIXPublicKey(publicKey)
if err != nil {
return "", nil, err
}

clientCert, _, err := x509ca.CreateClientCertificate(rootCA, subj, pkixPubKey, privKey)
if err != nil {
return "", nil, err
}

// Format in PEM
rootPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: rootCA.Raw,
})

return clientCert, []string{string(rootPEM)}, nil
}
2 changes: 2 additions & 0 deletions pkg/api/googleca_signing_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func GoogleCASigningCertHandler(ctx context.Context, subj *challenges.ChallengeR
privca = googleca.SpiffeSubject(subj.Value)
case challenges.GithubWorkflowValue:
privca = googleca.GithubWorkflowSubject(subj.Value)
case challenges.KubernetesValue:
privca = googleca.KubernetesSubject(subj.Value)
}

extensions := googleca.IssuerExtension(subj.Issuer)
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/pkcs11ca_signing_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"os"
"path/filepath"

"github.com/sigstore/fulcio/pkg/ca/pkcs11ca"
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/challenges"
"github.com/sigstore/fulcio/pkg/log"
"github.com/sigstore/fulcio/pkg/pkcs11"
Expand Down Expand Up @@ -74,7 +74,7 @@ func Pkcs11CASigningCertHandler(ctx context.Context, subj *challenges.ChallengeR
return "", nil, err
}

clientCert, _, err := pkcs11ca.CreateClientCertificate(rootCA, subj, pkixPubKey, privKey)
clientCert, _, err := x509ca.CreateClientCertificate(rootCA, subj, pkixPubKey, privKey)
if err != nil {
return "", nil, err
}
Expand Down
79 changes: 79 additions & 0 deletions pkg/ca/ephemeralca/ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package ephemeralca

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"math"
"math/big"
"time"
)

var (
ca *x509.Certificate
privKey interface{}
)

func Initialize(context.Context) error {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
privKey = priv

// TODO: We could make it so this could be passed in by the user
serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if err != nil {
return err
}
rootCA := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"sigstore"},
Country: []string{"USA"},
Province: []string{"WA"},
Locality: []string{"Kirkland"},
StreetAddress: []string{"767 6th St S"},
PostalCode: []string{"98033"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true, MaxPathLen: 1,
}

caBytes, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, priv.Public(), priv)
if err != nil {
return err
}

ca, err = x509.ParseCertificate(caBytes)
if err != nil {
return err
}

return nil
}

func CA() (*x509.Certificate, interface{}) {
return ca, privKey
}
8 changes: 8 additions & 0 deletions pkg/ca/googleca/googleca.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ func GithubWorkflowSubject(id string) *privatecapb.CertificateConfig_SubjectConf
}
}

func KubernetesSubject(id string) *privatecapb.CertificateConfig_SubjectConfig {
return &privatecapb.CertificateConfig_SubjectConfig{
SubjectAltName: &privatecapb.SubjectAltNames{
Uris: []string{id},
},
}
}

func IssuerExtension(issuer string) []*privatecapb.X509Extension {
if issuer == "" {
return nil
Expand Down
Loading

0 comments on commit 58ec2fa

Please sign in to comment.