Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to calling gRPC-based fulcio v2 APIs #1762

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions .github/workflows/kind-verify-attestation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ jobs:

- name: Sign demoimage with cosign
run: |
./cosign sign --rekor-url ${{ env.REKOR_URL }} --fulcio-url ${{ env.FULCIO_URL }} --yes --allow-insecure-registry ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}
./cosign sign --rekor-url ${{ env.REKOR_URL }} --fulcio-url ${{ env.FULCIO_GRPC_URL }} --yes --allow-insecure-fulcio --allow-insecure-registry ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}

- name: Create attestation for it
run: |
echo -n 'foobar e2e test' > ./predicate-file
./cosign attest --predicate ./predicate-file --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --yes ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}
./cosign attest --predicate ./predicate-file --fulcio-url ${{ env.FULCIO_GRPC_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-fulcio --allow-insecure-registry --yes ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}

- name: Verify with cosign
run: |
Expand Down Expand Up @@ -144,7 +144,7 @@ jobs:

- name: Create vuln attestation for it
run: |
./cosign attest --predicate ./test/testdata/attestations/vuln-predicate.json --type vuln --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --yes ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}
./cosign attest --predicate ./test/testdata/attestations/vuln-predicate.json --type vuln --fulcio-url ${{ env.FULCIO_GRPC_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-fulcio --allow-insecure-registry --yes ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}

- name: Verify vuln attestation with cosign, works
run: |
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func Attest() *cobra.Command {
FulcioURL: o.Fulcio.URL,
IDToken: o.Fulcio.IdentityToken,
InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify,
AllowFulcioInsecure: o.Fulcio.AllowInsecure,
RekorURL: o.Rekor.URL,
OIDCIssuer: o.OIDC.Issuer,
OIDCClientID: o.OIDC.ClientID,
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
}

opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
if sv.Cert != nil {
if sv.Cert != "" {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
}

Expand Down
105 changes: 83 additions & 22 deletions cmd/cosign/cli/fulcio/fulcio.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,26 @@ import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/url"
"os"
"strings"

"golang.org/x/term"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"

"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/internal/pkg/cosign/fulcio/fulcioroots"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/providers"
"github.com/sigstore/fulcio/pkg/api"
fulciopb "github.com/sigstore/fulcio/pkg/generated/protobuf"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/oauthflow"
"github.com/sigstore/sigstore/pkg/signature"
)
Expand Down Expand Up @@ -62,8 +69,8 @@ func (rf *realConnector) OIDConnect(url, clientID, secret, redirectURL string) (
return oauthflow.OIDConnect(url, clientID, secret, redirectURL, rf.flow)
}

func getCertForOauthID(priv *ecdsa.PrivateKey, fc api.LegacyClient, connector oidcConnector, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string) (*api.CertificateResponse, error) {
pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
func getCertForOauthID(ctx context.Context, priv *ecdsa.PrivateKey, fc fulciopb.CAClient, connector oidcConnector, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string) (*fulciopb.SigningCertificate, error) {
pubPEM, err := cryptoutils.MarshalPublicKeyToPEM(priv.Public())
if err != nil {
return nil, err
}
Expand All @@ -80,19 +87,28 @@ func getCertForOauthID(priv *ecdsa.PrivateKey, fc api.LegacyClient, connector oi
return nil, err
}

cr := api.CertificateRequest{
PublicKey: api.Key{
Algorithm: "ecdsa",
Content: pubBytes,
cscr := &fulciopb.CreateSigningCertificateRequest{
Key: &fulciopb.CreateSigningCertificateRequest_PublicKeyRequest{
PublicKeyRequest: &fulciopb.PublicKeyRequest{
PublicKey: &fulciopb.PublicKey{
Algorithm: fulciopb.PublicKeyAlgorithm_ECDSA,
bobcallaway marked this conversation as resolved.
Show resolved Hide resolved
Content: string(pubPEM),
},
ProofOfPossession: proof,
},
},
Credentials: &fulciopb.Credentials{
Credentials: &fulciopb.Credentials_OidcIdentityToken{
OidcIdentityToken: tok.RawString,
},
},
SignedEmailAddress: proof,
}

return fc.SigningCert(cr, tok.RawString)
return fc.CreateSigningCertificate(ctx, cscr)
}

// GetCert returns the PEM-encoded signature of the OIDC identity returned as part of an interactive oauth2 flow plus the PEM-encoded cert chain.
func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string, fClient api.LegacyClient) (*api.CertificateResponse, error) {
func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string, fClient fulciopb.CAClient) (*fulciopb.SigningCertificate, error) {
c := &realConnector{}
switch flow {
case flowDevice:
Expand All @@ -105,19 +121,19 @@ func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIss
return nil, fmt.Errorf("unsupported oauth flow: %s", flow)
}

return getCertForOauthID(priv, fClient, c, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL)
return getCertForOauthID(ctx, priv, fClient, c, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL)
}

type Signer struct {
Cert []byte
Chain []byte
Cert string
Chain []string
SCT []byte
pub *ecdsa.PublicKey
*signature.ECDSASignerVerifier
}

func NewSigner(ctx context.Context, ko options.KeyOpts) (*Signer, error) {
fClient, err := NewClient(ko.FulcioURL)
fClient, err := NewClient(ko)
if err != nil {
return nil, fmt.Errorf("creating Fulcio client: %w", err)
}
Expand Down Expand Up @@ -172,17 +188,23 @@ func NewSigner(ctx context.Context, ko options.KeyOpts) (*Signer, error) {
}
flow = flowNormal
}
Resp, err := GetCert(ctx, priv, idToken, flow, ko.OIDCIssuer, ko.OIDCClientID, ko.OIDCClientSecret, ko.OIDCRedirectURL, fClient) // TODO, use the chain.
resp, err := GetCert(ctx, priv, idToken, flow, ko.OIDCIssuer, ko.OIDCClientID, ko.OIDCClientSecret, ko.OIDCRedirectURL, fClient)
if err != nil {
return nil, fmt.Errorf("retrieving cert: %w", err)
}

f := &Signer{
pub: &priv.PublicKey,
ECDSASignerVerifier: signer,
Cert: Resp.CertPEM,
Chain: Resp.ChainPEM,
SCT: Resp.SCT,
}
switch csc := resp.Certificate.(type) {
case *fulciopb.SigningCertificate_SignedCertificateDetachedSct:
f.SCT = csc.SignedCertificateDetachedSct.SignedCertificateTimestamp
f.Cert = csc.SignedCertificateDetachedSct.Chain.Certificates[0]
f.Chain = csc.SignedCertificateDetachedSct.Chain.Certificates[1:]
case *fulciopb.SigningCertificate_SignedCertificateEmbeddedSct:
f.Cert = csc.SignedCertificateEmbeddedSct.Chain.Certificates[0]
f.Chain = csc.SignedCertificateEmbeddedSct.Chain.Certificates[1:]
}

return f, nil
Expand All @@ -202,11 +224,50 @@ func GetIntermediates() (*x509.CertPool, error) {
return fulcioroots.GetIntermediates()
}

func NewClient(fulcioURL string) (api.LegacyClient, error) {
fulcioServer, err := url.Parse(fulcioURL)
func NewClient(ko options.KeyOpts) (fulciopb.CAClient, error) {
opts := []grpc.DialOption{grpc.WithUserAgent(options.UserAgent())}
// follows https://github.com/grpc/grpc/blob/master/doc/naming.md
// if port was not specified, use 443
// if port is 443, presume TLS
host := ""
port := "443"
// if the url has a scheme, let's parse it with url.Parse
switch {
case strings.Contains(ko.FulcioURL, "://"):
fulcioServer, err := url.Parse(ko.FulcioURL)
if err != nil {
return nil, err
}
host = fulcioServer.Hostname()
if fulcioServer.Port() != "" {
port = fulcioServer.Port()
} else if fulcioServer.Scheme == "http" && ko.AllowFulcioInsecure {
port = "80"
}
case strings.Contains(ko.FulcioURL, ":"):
// if the url does not have a scheme, but has a colon, let's split host and port
parsedHost, parsedPort, err := net.SplitHostPort(ko.FulcioURL)
if err != nil {
return nil, err
}
host = parsedHost
port = parsedPort
default:
// the url does not have a scheme or a colon, let's assume it's just a hostname
host = ko.FulcioURL
}
target := fmt.Sprintf("%v:%v", host, port)

// assume TLS unless flag explicitly says to use insecure
transportCreds := credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})
if ko.AllowFulcioInsecure {
transportCreds = insecure.NewCredentials()
}

opts = append(opts, grpc.WithTransportCredentials(transportCreds))
conn, err := grpc.Dial(target, opts...)
if err != nil {
return nil, err
}
fClient := api.NewClient(fulcioServer, api.WithUserAgent(options.UserAgent()))
return fClient, nil
return fulciopb.NewCAClient(conn), nil
}
56 changes: 35 additions & 21 deletions cmd/cosign/cli/fulcio/fulcio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package fulcio

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
Expand All @@ -26,8 +27,9 @@ import (
"testing"

"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/fulcio/pkg/api"
fulciopb "github.com/sigstore/fulcio/pkg/generated/protobuf"
"github.com/sigstore/sigstore/pkg/oauthflow"
"google.golang.org/grpc"
)

type testFlow struct {
Expand All @@ -44,21 +46,26 @@ func (tf *testFlow) OIDConnect(url, clientID, secret, redirectURL string) (*oaut
}

type testClient struct {
payload api.CertificateResponse
rootResp api.RootResponse
payload fulciopb.SigningCertificate
rootResp fulciopb.TrustBundle
config fulciopb.Configuration
err error
}

var _ api.LegacyClient = (*testClient)(nil)
var _ fulciopb.CAClient = (*testClient)(nil)

func (p *testClient) SigningCert(cr api.CertificateRequest, token string) (*api.CertificateResponse, error) {
func (p *testClient) CreateSigningCertificate(_ context.Context, _ *fulciopb.CreateSigningCertificateRequest, _ ...grpc.CallOption) (*fulciopb.SigningCertificate, error) {
return &p.payload, p.err
}

func (p *testClient) RootCert() (*api.RootResponse, error) {
func (p *testClient) GetTrustBundle(_ context.Context, _ *fulciopb.GetTrustBundleRequest, _ ...grpc.CallOption) (*fulciopb.TrustBundle, error) {
return &p.rootResp, p.err
}

func (p *testClient) GetConfiguration(_ context.Context, _ *fulciopb.GetConfigurationRequest, _ ...grpc.CallOption) (*fulciopb.Configuration, error) {
return &p.config, p.err
}

func TestGetCertForOauthID(t *testing.T) {
testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
Expand Down Expand Up @@ -103,9 +110,14 @@ func TestGetCertForOauthID(t *testing.T) {
expectedCertBytes := pem.EncodeToMemory(expectedCertPem)
expectedExtraBytes := []byte("0123456789abcdef")
tscp := &testClient{
payload: api.CertificateResponse{
CertPEM: expectedCertBytes,
ChainPEM: expectedExtraBytes,
payload: fulciopb.SigningCertificate{
Certificate: &fulciopb.SigningCertificate_SignedCertificateDetachedSct{
SignedCertificateDetachedSct: &fulciopb.SigningCertificateDetachedSCT{
Chain: &fulciopb.CertificateChain{
Certificates: []string{string(expectedCertBytes), string(expectedExtraBytes)},
},
},
},
},
err: tc.signingCertErr,
}
Expand All @@ -118,25 +130,27 @@ func TestGetCertForOauthID(t *testing.T) {
err: tc.tokenGetterErr,
}

resp, err := getCertForOauthID(testKey, tscp, &tf, "", "", "", "")

resp, err := getCertForOauthID(context.Background(), testKey, tscp, &tf, "", "", "", "")
if err != nil {
if !tc.expectErr {
t.Fatalf("getCertForOauthID returned error: %v", err)
}
return
}

leaf := resp.GetSignedCertificateDetachedSct().Chain.Certificates[0]
extra := resp.GetSignedCertificateDetachedSct().Chain.Certificates[1]
if tc.expectErr {
t.Fatalf("getCertForOauthID got: %q, %q wanted error", resp.CertPEM, resp.ChainPEM)
t.Fatalf("getCertForOauthID got: %q, %q wanted error", leaf, extra)
}

expectedCert := string(expectedCertBytes)
actualCert := string(resp.CertPEM)
actualCert := leaf
if actualCert != expectedCert {
t.Errorf("getCertForOauthID returned cert %q, wanted %q", actualCert, expectedCert)
}
expectedChain := string(expectedExtraBytes)
actualChain := string(resp.ChainPEM)
actualChain := extra
if actualChain != expectedChain {
t.Errorf("getCertForOauthID returned chain %q, wanted %q", actualChain, expectedChain)
}
Expand All @@ -146,28 +160,28 @@ func TestGetCertForOauthID(t *testing.T) {

func TestNewClient(t *testing.T) {
t.Parallel()
expectedUserAgent := options.UserAgent()
requestReceived := false
testServer := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
requestReceived = true
file := []byte{}

got := r.UserAgent()
if got != expectedUserAgent {
t.Errorf("wanted User-Agent %q, got %q", expectedUserAgent, got)
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(file)
}))
defer testServer.Close()

client, err := NewClient(testServer.URL)
keyOpts := options.KeyOpts{
AllowFulcioInsecure: true,
FulcioURL: testServer.URL,
}

client, err := NewClient(keyOpts)
if err != nil {
t.Error(err)
}

_, _ = client.SigningCert(api.CertificateRequest{}, "")
_, _ = client.CreateSigningCertificate(context.Background(), &fulciopb.CreateSigningCertificateRequest{})

if !requestReceived {
t.Fatal("no requests were received")
Expand Down
3 changes: 2 additions & 1 deletion cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio/fulcioverifier/ctl"
Expand All @@ -32,7 +33,7 @@ func NewSigner(ctx context.Context, ko options.KeyOpts) (*fulcio.Signer, error)
}

// verify the sct
if err := ctl.VerifySCT(ctx, fs.Cert, fs.Chain, fs.SCT); err != nil {
if err := ctl.VerifySCT(ctx, []byte(fs.Cert), []byte(strings.Join(fs.Chain, "\n")), fs.SCT); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Do you want to add a TODO here to change VerifySCT to take the list of certs rather than a single PEM-encoded chain? No need to change it now, but it would be better than converting back and forth going forward

return nil, fmt.Errorf("verifying SCT: %w", err)
}
fmt.Fprintln(os.Stderr, "Successfully verified SCT...")
Expand Down
Loading