forked from sigstore/cosign
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This mostly copies the functionality of image attestation and blob signing. Signed-off-by: Michael <mlieberman85@gmail.com>
- Loading branch information
1 parent
1258512
commit 5565dee
Showing
4 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package attest | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/sigstore/cosign/cmd/cosign/cli/options" | ||
"github.com/sigstore/cosign/cmd/cosign/cli/sign" | ||
"github.com/sigstore/cosign/pkg/cosign" | ||
"github.com/sigstore/cosign/pkg/cosign/attestation" | ||
"github.com/sigstore/cosign/pkg/oci/static" | ||
"github.com/sigstore/cosign/pkg/types" | ||
"github.com/sigstore/rekor/pkg/generated/client" | ||
"github.com/sigstore/rekor/pkg/generated/models" | ||
"github.com/sigstore/sigstore/pkg/signature" | ||
"github.com/sigstore/sigstore/pkg/signature/dsse" | ||
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" | ||
) | ||
|
||
func AttestBlobCmd(ctx context.Context, ko options.KeyOpts, artifactPath string, artifactHash string, certPath string, certChainPath string, noUpload bool, predicatePath string, force bool, predicateType string, replace bool, timeout time.Duration) error { | ||
// A key file or token is required unless we're in experimental mode! | ||
if options.EnableExperimental() { | ||
if options.NOf(ko.KeyRef, ko.Sk) > 1 { | ||
return &options.KeyParseError{} | ||
} | ||
} else { | ||
if !options.OneOf(ko.KeyRef, ko.Sk) { | ||
return &options.KeyParseError{} | ||
} | ||
} | ||
|
||
var artifact []byte | ||
var hexDigest string | ||
var err error | ||
|
||
if artifactHash == "" { | ||
if artifactPath == "-" { | ||
artifact, err = io.ReadAll(os.Stdin) | ||
} else { | ||
fmt.Fprintln(os.Stderr, "Using payload from:", artifactPath) | ||
artifact, err = os.ReadFile(filepath.Clean(artifactPath)) | ||
} | ||
if err != nil { | ||
return err | ||
} else if timeout != 0 { | ||
var cancelFn context.CancelFunc | ||
ctx, cancelFn = context.WithTimeout(ctx, timeout) | ||
defer cancelFn() | ||
} | ||
} | ||
|
||
sv, err := sign.SignerFromKeyOpts(ctx, certPath, certChainPath, ko) | ||
if err != nil { | ||
return errors.Wrap(err, "getting signer") | ||
} | ||
defer sv.Close() | ||
//pub, err := sv.PublicKey() | ||
if err != nil { | ||
return err | ||
} | ||
/*pem, err := cryptoutils.MarshalPublicKeyToPEM(pub) | ||
if err != nil { | ||
return errors.Wrap(err, "key to pem") | ||
}*/ | ||
|
||
if timeout != 0 { | ||
var cancelFn context.CancelFunc | ||
ctx, cancelFn = context.WithTimeout(ctx, timeout) | ||
defer cancelFn() | ||
} | ||
|
||
if artifactHash == "" { | ||
digest, _, err := signature.ComputeDigestForSigning(bytes.NewReader(artifact), crypto.SHA256, []crypto.Hash{crypto.SHA256, crypto.SHA384}) | ||
if err != nil { | ||
return err | ||
} | ||
hexDigest = strings.ToLower(hex.EncodeToString(digest)) | ||
} else { | ||
hexDigest = artifactHash | ||
} | ||
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType) | ||
|
||
fmt.Fprintln(os.Stderr, "Using payload from:", predicatePath) | ||
predicate, err := os.Open(predicatePath) | ||
if err != nil { | ||
return err | ||
} | ||
defer predicate.Close() | ||
|
||
base := path.Base(artifactPath) | ||
|
||
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{ | ||
Predicate: predicate, | ||
Type: predicateType, | ||
Digest: hexDigest, | ||
Repo: base, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
payload, err := json.Marshal(sh) | ||
if err != nil { | ||
return err | ||
} | ||
signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) | ||
if err != nil { | ||
return errors.Wrap(err, "signing") | ||
} | ||
|
||
if noUpload { | ||
fmt.Println(string(signedPayload)) | ||
return nil | ||
} | ||
|
||
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} | ||
if sv.Cert != nil { | ||
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) | ||
} | ||
|
||
// Check whether we should be uploading to the transparency log | ||
if options.EnableExperimental() { | ||
fmt.Println("Uploading to Rekor") | ||
/*r, err := rc.GetRekorClient(ko.RekorURL) | ||
if err != nil { | ||
return err | ||
}*/ | ||
_, err := uploadToTlog(ctx, sv, ko.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { | ||
return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
/*l, err := cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, pem) | ||
if err != nil { | ||
return err | ||
}*/ | ||
|
||
//fmt.Fprintln(os.Stderr, "Log id:", *bundle.LogIndex) | ||
} | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package cli | ||
|
||
import ( | ||
"github.com/pkg/errors" | ||
"github.com/sigstore/cosign/cmd/cosign/cli/attest" | ||
"github.com/sigstore/cosign/cmd/cosign/cli/generate" | ||
"github.com/sigstore/cosign/cmd/cosign/cli/options" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func AttestBlob() *cobra.Command { | ||
o := &options.AttestBlobOptions{} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "attest-blob", | ||
Short: "Attest the supplied blob.", | ||
Example: ` cosign attest-blob --key <key path>|<kms uri> [--predicate <path>] [--a key=value] [--no-upload=true|false] [--f] [--r] <BLOB uri> | ||
# attach an attestation to a blob Google sign-in (experimental) | ||
COSIGN_EXPERIMENTAL=1 cosign attest-blob --timeout 90s --predicate <FILE> --type <TYPE> <BLOB> | ||
# attach an attestation to a blob with a local key pair file | ||
cosign attest-blob --predicate <FILE> --type <TYPE> --key cosign.key <BLOB> | ||
# attach an attestation to a blob with a key pair stored in Azure Key Vault | ||
cosign attest-blob --predicate <FILE> --type <TYPE> --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] <BLOB> | ||
# attach an attestation to a blob with a key pair stored in AWS KMS | ||
cosign attest-blob --predicate <FILE> --type <TYPE> --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] <BLOB> | ||
# attach an attestation to a blob with a key pair stored in Google Cloud KMS | ||
cosign attest-blob --predicate <FILE> --type <TYPE> --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] <BLOB> | ||
# attach an attestation to a blob with a key pair stored in Hashicorp Vault | ||
cosign attest-blob --predicate <FILE> --type <TYPE> --key hashivault://[KEY] <BLOB>`, | ||
|
||
Args: cobra.MinimumNArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
oidcClientSecret, err := o.OIDC.ClientSecret() | ||
if err != nil { | ||
return err | ||
} | ||
ko := options.KeyOpts{ | ||
KeyRef: o.Key, | ||
PassFunc: generate.GetPass, | ||
Sk: o.SecurityKey.Use, | ||
Slot: o.SecurityKey.Slot, | ||
FulcioURL: o.Fulcio.URL, | ||
IDToken: o.Fulcio.IdentityToken, | ||
InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, | ||
RekorURL: o.Rekor.URL, | ||
OIDCIssuer: o.OIDC.Issuer, | ||
OIDCClientID: o.OIDC.ClientID, | ||
OIDCClientSecret: oidcClientSecret, | ||
OIDCRedirectURL: o.OIDC.RedirectURL, | ||
} | ||
for _, artifact := range args { | ||
if err := attest.AttestBlobCmd(cmd.Context(), ko, artifact, o.Hash, o.Cert, o.CertChain, o.NoUpload, | ||
o.Predicate.Path, o.Force, o.Predicate.Type, o.Replace, ro.Timeout); err != nil { | ||
return errors.Wrapf(err, "attesting %s", artifact) | ||
} | ||
} | ||
return nil | ||
}, | ||
} | ||
o.AddFlags(cmd) | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 options | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
// AttestOptions is the top level wrapper for the attest command. | ||
type AttestBlobOptions struct { | ||
Key string | ||
Cert string | ||
CertChain string | ||
NoUpload bool | ||
Force bool | ||
Recursive bool | ||
Replace bool | ||
Timeout time.Duration | ||
Hash string | ||
|
||
Rekor RekorOptions | ||
Fulcio FulcioOptions | ||
OIDC OIDCOptions | ||
SecurityKey SecurityKeyOptions | ||
Predicate PredicateLocalOptions | ||
} | ||
|
||
var _ Interface = (*AttestOptions)(nil) | ||
|
||
// AddFlags implements Interface | ||
func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { | ||
o.SecurityKey.AddFlags(cmd) | ||
o.Predicate.AddFlags(cmd) | ||
o.Fulcio.AddFlags(cmd) | ||
o.OIDC.AddFlags(cmd) | ||
o.Rekor.AddFlags(cmd) | ||
|
||
cmd.Flags().StringVar(&o.Key, "key", "", | ||
"path to the private key file, KMS URI or Kubernetes Secret") | ||
|
||
cmd.Flags().StringVar(&o.Cert, "cert", "", | ||
"path to the x509 certificate to include in the Signature") | ||
|
||
cmd.Flags().StringVar(&o.CertChain, "cert-chain", "", | ||
"path to a list of CA X.509 certificates in PEM format which will be needed "+ | ||
"when building the certificate chain for the signing certificate. "+ | ||
"Must start with the parent intermediate CA certificate of the "+ | ||
"signing certificate and end with the root certificate. Included in the OCI Signature") | ||
|
||
cmd.Flags().BoolVar(&o.NoUpload, "no-upload", false, | ||
"do not upload the generated attestation") | ||
|
||
cmd.Flags().BoolVarP(&o.Force, "force", "f", false, | ||
"skip warnings and confirmations") | ||
|
||
cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, | ||
"") | ||
|
||
cmd.Flags().DurationVar(&o.Timeout, "timeout", time.Second*30, | ||
"HTTP Timeout defaults to 30 seconds") | ||
|
||
cmd.Flags().StringVar(&o.Hash, "hash", "", | ||
"hash of blob in hexadecimal (base16). Used if you want to sign an artifact stored elsewhere and have the hash") | ||
} |