Skip to content

Commit

Permalink
[SECURESIGN-1401] Implement Fulcio cert rotation scenario
Browse files Browse the repository at this point in the history
  • Loading branch information
bouskaJ committed Nov 11, 2024
1 parent a5f7217 commit d2a497e
Show file tree
Hide file tree
Showing 7 changed files with 574 additions and 1 deletion.
12 changes: 12 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ jobs:
registry: registry.redhat.io
auth_file_path: /tmp/config.json

# install tuftool
- name: Checkout tough source
uses: actions/checkout@v4
with:
repository: "securesign/tough"
path: tough
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- run: cd tough && cargo build --release && echo "$PWD/target/release" >> $GITHUB_PATH

- name: Install Cluster
uses: container-tools/[email protected]
with:
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/sigstore/fulcio v1.4.4
github.com/sigstore/sigstore v1.8.1
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
golang.org/x/net v0.25.0
google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.33.0
Expand Down Expand Up @@ -64,6 +65,7 @@ require (
github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand All @@ -81,7 +83,6 @@ require (
github.com/vbatts/tar-split v0.11.3 // indirect
go.uber.org/goleak v1.3.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
Expand Down Expand Up @@ -66,6 +68,7 @@ github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4
github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
Expand All @@ -90,6 +93,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
280 changes: 280 additions & 0 deletions test/e2e/fulcio_key_rotation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
//go:build integration

package e2e

import (
"context"
"os"
"os/exec"
"path/filepath"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/securesign/operator/api/v1alpha1"
"github.com/securesign/operator/internal/controller/common/utils"
"github.com/securesign/operator/internal/controller/common/utils/kubernetes"
"github.com/securesign/operator/internal/controller/constants"
tufAction "github.com/securesign/operator/internal/controller/tuf/actions"
"github.com/securesign/operator/test/e2e/support"
kubernetes2 "github.com/securesign/operator/test/e2e/support/kubernetes"
"github.com/securesign/operator/test/e2e/support/tas"
clients "github.com/securesign/operator/test/e2e/support/tas/cli"
"github.com/securesign/operator/test/e2e/support/tas/fulcio"
"github.com/securesign/operator/test/e2e/support/tas/securesign"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
runtimeCli "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)

var _ = Describe("Fulcio cert rotation test", Ordered, func() {
cli, _ := support.CreateClient()
ctx := context.TODO()
var (
targetImageName string
namespace *v1.Namespace
s *v1alpha1.Securesign
oldCert []byte
newCert *v1.Secret
err error
)

AfterEach(func() {
if CurrentSpecReport().Failed() && support.IsCIEnvironment() {
support.DumpNamespace(ctx, cli, namespace.Name)
}
})

BeforeAll(func() {
if _, err := exec.LookPath("tuftool"); err != nil {
Skip("tuftool command not found")
}
namespace = support.CreateTestNamespace(ctx, cli)
DeferCleanup(func() {
_ = cli.Delete(ctx, namespace)
})

s = &v1alpha1.Securesign{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace.Name,
Name: "test",
Annotations: map[string]string{
"rhtas.redhat.com/metrics": "false",
},
},
Spec: v1alpha1.SecuresignSpec{
Rekor: v1alpha1.RekorSpec{
ExternalAccess: v1alpha1.ExternalAccess{
Enabled: true,
},
RekorSearchUI: v1alpha1.RekorSearchUI{
Enabled: utils.Pointer(true),
},
},
Fulcio: v1alpha1.FulcioSpec{
ExternalAccess: v1alpha1.ExternalAccess{
Enabled: true,
},
Config: v1alpha1.FulcioConfig{
OIDCIssuers: []v1alpha1.OIDCIssuer{
{
ClientID: support.OidcClientID(),
IssuerURL: support.OidcIssuerUrl(),
Issuer: support.OidcIssuerUrl(),
Type: "email",
},
}},
Certificate: v1alpha1.FulcioCert{
OrganizationName: "MyOrg",
OrganizationEmail: "[email protected]",
CommonName: "fulcio",
},
},
Ctlog: v1alpha1.CTlogSpec{},
Tuf: v1alpha1.TufSpec{
ExternalAccess: v1alpha1.ExternalAccess{
Enabled: true,
},
},
Trillian: v1alpha1.TrillianSpec{Db: v1alpha1.TrillianDB{
Create: ptr.To(true),
}},
TimestampAuthority: &v1alpha1.TimestampAuthoritySpec{
ExternalAccess: v1alpha1.ExternalAccess{
Enabled: true,
},
Signer: v1alpha1.TimestampAuthoritySigner{
CertificateChain: v1alpha1.CertificateChain{
RootCA: &v1alpha1.TsaCertificateAuthority{
OrganizationName: "MyOrg",
OrganizationEmail: "[email protected]",
CommonName: "tsa.hostname",
},
IntermediateCA: []*v1alpha1.TsaCertificateAuthority{
{
OrganizationName: "MyOrg",
OrganizationEmail: "[email protected]",
CommonName: "tsa.hostname",
},
},
LeafCA: &v1alpha1.TsaCertificateAuthority{
OrganizationName: "MyOrg",
OrganizationEmail: "[email protected]",
CommonName: "tsa.hostname",
},
},
},
NTPMonitoring: v1alpha1.NTPMonitoring{
Enabled: true,
Config: &v1alpha1.NtpMonitoringConfig{
RequestAttempts: 3,
RequestTimeout: 5,
NumServers: 4,
ServerThreshold: 3,
MaxTimeDelta: 6,
Period: 60,
Servers: []string{"time.apple.com", "time.google.com", "time-a-b.nist.gov", "time-b-b.nist.gov", "gbg1.ntp.se"},
},
},
},
},
}
})

BeforeAll(func() {
targetImageName = support.PrepareImage(ctx)
})

Describe("Install with autogenerated certificates", func() {
BeforeAll(func() {
Expect(cli.Create(ctx, s)).To(Succeed())
})

It("All other components are running", func() {
tas.VerifyAllComponents(ctx, cli, s, true)
})

It("Use cosign cli", func() {
tas.VerifyByCosign(ctx, cli, s, targetImageName)
})
})

Describe("Fulcio cert rotation", func() {

It("Download fulcio cert", func() {
f := fulcio.Get(ctx, cli, namespace.Name, s.Name)()
Expect(f).ToNot(BeNil())
oldCert, err = kubernetes.GetSecretData(cli, namespace.Name, f.Status.Certificate.CARef)
Expect(err).ToNot(HaveOccurred())
Expect(oldCert).ToNot(BeEmpty())
})

It("Update fulcio cert", func() {
secretName := "new-fulcio-cert"
newCert = fulcio.CreateSecret(namespace.Name, secretName)
Expect(cli.Create(ctx, newCert)).To(Succeed())

Eventually(func(g Gomega) error {
f := securesign.Get(ctx, cli, namespace.Name, s.Name)()
g.Expect(f).ToNot(BeNil())
f.Spec.Fulcio.Certificate.PrivateKeyRef = &v1alpha1.SecretKeySelector{
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: secretName,
},
Key: "private",
}

f.Spec.Fulcio.Certificate.PrivateKeyPasswordRef = &v1alpha1.SecretKeySelector{
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: secretName,
},
Key: "password",
}

f.Spec.Fulcio.Certificate.CARef = &v1alpha1.SecretKeySelector{
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: secretName,
},
Key: "cert",
}

f.Spec.Ctlog.RootCertificates = []v1alpha1.SecretKeySelector{
{
LocalObjectReference: v1alpha1.LocalObjectReference{
Name: secretName,
},
Key: "cert",
},
}

return cli.Update(ctx, f)
}).Should(Succeed())

// wait a moment for redeploy
time.Sleep(10 * time.Second)
tas.VerifyAllComponents(ctx, cli, s, true)
})

It("Update TUF repository", func() {
certs, err := os.MkdirTemp(os.TempDir(), "certs")
Expect(err).ToNot(HaveOccurred())

Expect(os.WriteFile(certs+"/new-fulcio.cert.pem", newCert.Data["cert"], 0644)).To(Succeed())
Expect(os.WriteFile(certs+"/fulcio_v1.crt.pem", oldCert, 0644)).To(Succeed())

tufRepoWorkdir, err := os.MkdirTemp(os.TempDir(), "tuf-repo")
Expect(err).ToNot(HaveOccurred())

tufKeys := &v1.Secret{}
Expect(os.Mkdir(filepath.Join(tufRepoWorkdir, "keys"), 0777)).To(Succeed())
Expect(cli.Get(ctx, runtimeCli.ObjectKey{Name: "tuf-root-keys", Namespace: namespace.Name}, tufKeys)).To(Succeed())
for k, v := range tufKeys.Data {
Expect(os.WriteFile(filepath.Join(tufRepoWorkdir, "keys", k), v, 0644)).To(Succeed())
}

Expect(os.Mkdir(filepath.Join(tufRepoWorkdir, "tuf-repo"), 0777)).To(Succeed())
tufPodList := &v1.PodList{}
Expect(cli.List(ctx, tufPodList, runtimeCli.InNamespace(namespace.Name), runtimeCli.MatchingLabels{constants.LabelAppComponent: tufAction.ComponentName})).To(Succeed())
Expect(tufPodList.Items).To(HaveLen(1))

Expect(kubernetes2.CopyFromPod(tufPodList.Items[0], "/var/www/html", filepath.Join(tufRepoWorkdir, "tuf-repo"))).To(Succeed())

Expect(clients.ExecuteInDir(certs, "tuftool", tufToolParams("fulcio_v1.crt.pem", tufRepoWorkdir, true)...)).To(Succeed())
Expect(clients.ExecuteInDir(certs, "tuftool", tufToolParams("new-fulcio.cert.pem", tufRepoWorkdir, false)...)).To(Succeed())

Expect(kubernetes2.CopyToPod(ctx, config.GetConfigOrDie(), tufPodList.Items[0], filepath.Join(tufRepoWorkdir, "tuf-repo"), "/var/www/html")).To(Succeed())
})

It("All other components are running", func() {
tas.VerifyAllComponents(ctx, cli, s, true)
})

It("Use cosign cli", func() {
tas.VerifyByCosign(ctx, cli, s, targetImageName)
newImage := support.PrepareImage(ctx)
tas.VerifyByCosign(ctx, cli, s, newImage)
})
})

})

func tufToolParams(targetName string, workdir string, expire bool) []string {
args := []string{
"rhtas",
"--root", workdir + "/tuf-repo/root.json",
"--key", workdir + "/keys/snapshot.pem",
"--key", workdir + "/keys/targets.pem",
"--key", workdir + "/keys/timestamp.pem",
"--set-fulcio-target", targetName,
"--fulcio-uri", "https://fulcio.rhtas",
"--outdir", workdir + "/tuf-repo",
"--metadata-url", "file://" + workdir + "/tuf-repo",
}

if expire {
args = append(args, "--fulcio-status", "Expired")
}
return args
}
Loading

0 comments on commit d2a497e

Please sign in to comment.