From 0c4cf2e9bb7dac8f8ed57d600075e47f79cd087d Mon Sep 17 00:00:00 2001 From: Zack Newman Date: Sun, 24 Apr 2022 12:01:05 -0400 Subject: [PATCH] Allow passing keys via environment variables (`env://` refs) (#1794) * Bump github.com/sigstore/sigstore Signed-off-by: Zachary Newman * test: add test for blob.LoadFileOrURL Signed-off-by: Zachary Newman * refactor: break up LoadFileOrURL by scheme Signed-off-by: Zachary Newman * feat: add "env://" scheme for blob load Signed-off-by: Zachary Newman * feat: add "env://" scheme for key lookup Tested manually as well: ```shell $ export COSIGN_PASSWORD=foo $ cosign generate-key-pair Enter password for private key: Enter password for private key again: Private key written to cosign.key Public key written to cosign.pub $ export MYPRIVKEY="$(cat cosign.key)" $ export MYPUBKEY="$(cat cosign.pub)" $ cosign verify-blob --key env://MYPUBKEY /dev/null --signature <(cosign sign-blob --key env://MYPRIVKEY /dev/null) Using payload from: /dev/null tlog entry created with index: 2095539 tlog entry verified with uuid: dd55086556d7ac0cded8f50961b68f7740e1435fbc5bb47460a8d78321313c7d index: 2095539 Verified OK ``` Signed-off-by: Zachary Newman * test: skip test that flakes on Windows Signed-off-by: Zachary Newman --- cmd/cosign/cli/sign.go | 3 ++ cmd/cosign/cli/verify.go | 3 ++ doc/cosign_sign.md | 3 ++ doc/cosign_verify.md | 3 ++ go.mod | 8 +-- go.sum | 34 ++++++------- pkg/blob/load.go | 37 ++++++++++---- pkg/blob/load_test.go | 99 ++++++++++++++++++++++++++++++++++++++ pkg/signature/keys.go | 12 +++-- pkg/signature/keys_test.go | 29 +++++++++++ 10 files changed, 194 insertions(+), 37 deletions(-) create mode 100644 pkg/blob/load_test.go diff --git a/cmd/cosign/cli/sign.go b/cmd/cosign/cli/sign.go index 3acdc1b2b61..f0f2ea19129 100644 --- a/cmd/cosign/cli/sign.go +++ b/cmd/cosign/cli/sign.go @@ -47,6 +47,9 @@ func Sign() *cobra.Command { # sign a container image and add annotations cosign sign --key cosign.key -a key1=value1 -a key2=value2 + # sign a container image with a key stored in an environment variable + cosign sign --key env://[ENV_VAR] + # sign a container image with a key pair stored in Azure Key Vault cosign sign --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index e48b4175e6c..6f180e7c549 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -61,6 +61,9 @@ against the transparency log.`, # verify image with public key provided by URL cosign verify --key https://host.for/[FILE] + # verify image with a key stored in an environment variable + cosign verify --key env://[ENV_VAR] + # verify image with public key stored in Google Cloud KMS cosign verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] diff --git a/doc/cosign_sign.md b/doc/cosign_sign.md index c4d3f26e0ff..88230c10524 100644 --- a/doc/cosign_sign.md +++ b/doc/cosign_sign.md @@ -27,6 +27,9 @@ cosign sign [flags] # sign a container image and add annotations cosign sign --key cosign.key -a key1=value1 -a key2=value2 + # sign a container image with a key stored in an environment variable + cosign sign --key env://[ENV_VAR] + # sign a container image with a key pair stored in Azure Key Vault cosign sign --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index 68bf94f6d90..9de4dbad45f 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -44,6 +44,9 @@ cosign verify [flags] # verify image with public key provided by URL cosign verify --key https://host.for/[FILE] + # verify image with a key stored in an environment variable + cosign verify --key env://[ENV_VAR] + # verify image with public key stored in Google Cloud KMS cosign verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] diff --git a/go.mod b/go.mod index 0bf5857d0fa..eaed295cbb3 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/secure-systems-lab/go-securesystemslib v0.3.1 github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 - github.com/sigstore/sigstore v1.2.1-0.20220401110139-0e610e39782f + github.com/sigstore/sigstore v1.2.1-0.20220424143412-3d41663116d5 github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.11.0 github.com/spiffe/go-spiffe/v2 v2.0.0 @@ -113,9 +113,9 @@ require ( cloud.google.com/go/kms v1.4.0 // indirect contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect - github.com/Azure/azure-sdk-for-go v63.2.0+incompatible // indirect + github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.25 // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect @@ -131,7 +131,7 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.43.37 // indirect + github.com/aws/aws-sdk-go v1.43.45 // indirect github.com/aws/aws-sdk-go-v2 v1.14.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.14.0 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.9.0 // indirect diff --git a/go.sum b/go.sum index 3fa87f6e4f9..4e331a4adaa 100644 --- a/go.sum +++ b/go.sum @@ -131,9 +131,8 @@ github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/azure-sdk-for-go v60.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v60.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v62.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v63.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v63.2.0+incompatible h1:OIqkK/zTGqVUuzpEvY0B1YSYDRAFC/j+y0w2GovCggI= -github.com/Azure/azure-sdk-for-go v63.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= +github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU= github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= @@ -154,8 +153,8 @@ github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgq github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= -github.com/Azure/go-autorest/autorest v0.11.25 h1:yp+V8DGur2aIUE87ebP8twPLz6k68jtJTlg61mEoByA= -github.com/Azure/go-autorest/autorest v0.11.25/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.4/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= @@ -325,9 +324,8 @@ github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zK github.com/aws/aws-sdk-go v1.42.8/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.42.22/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.42.25/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs= -github.com/aws/aws-sdk-go v1.43.30/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.43.37 h1:kyZ7UjaPZaCik+asF33UFOOYSwr9liDRr/UM/vuw8yY= -github.com/aws/aws-sdk-go v1.43.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.43.45 h1:2708Bj4uV+ym62MOtBnErm/CDX61C4mFe9V2gXy1caE= +github.com/aws/aws-sdk-go v1.43.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= github.com/aws/aws-sdk-go-v2 v1.11.0/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= @@ -951,8 +949,8 @@ github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-rod/rod v0.101.8/go.mod h1:N/zlT53CfSpq74nb6rOR0K8UF0SPUPBmzBnArrms+mY= -github.com/go-rod/rod v0.104.4 h1:sQR35AFo9ceR7ksh+Ld81bQzIbrXlQH/IO46iCWqxts= -github.com/go-rod/rod v0.104.4/go.mod h1:trmrxxg+qUodIIQiYeyJbW5ZMo0FSajmdEGw2tHzlM4= +github.com/go-rod/rod v0.106.1 h1:+9YdoTT56KI3KrFfWVr3I13wh0qbhm/Aq+7JvCBA6AQ= +github.com/go-rod/rod v0.106.1/go.mod h1:+YLe2X+nAuEGpYWs7rKPZr9SMX100FbxYZaeU1Dofpc= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -2034,8 +2032,8 @@ github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 h1:mbqXrm8YZXN/cJ github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3/go.mod h1:u9clLqaVjqV9pExVL1XkM37dGyMCOX/LMocS9nsnWDY= github.com/sigstore/sigstore v1.0.2-0.20211210190220-04746d994282/go.mod h1:SuM+QIHtnnR9eGsURRLv5JfxM6KeaU0XKA1O7FmLs4Q= github.com/sigstore/sigstore v1.1.0/go.mod h1:gDpcHw4VwpoL5C6N1Ud1YtBsc+ikRDwDelDlWRyYoE8= -github.com/sigstore/sigstore v1.2.1-0.20220401110139-0e610e39782f h1:JPD9q1718mub78ILVcTqOZ/q4ECKCQ7JQfUX/q+nEJ4= -github.com/sigstore/sigstore v1.2.1-0.20220401110139-0e610e39782f/go.mod h1:9wYagRiKz/8KgK/YFPM6FA8WrNjv3Y6rQUQWBLqJXs0= +github.com/sigstore/sigstore v1.2.1-0.20220424143412-3d41663116d5 h1:8OL06Knchax4CMtdfquC3ASWQPtYMJgyeQImWQPw6XE= +github.com/sigstore/sigstore v1.2.1-0.20220424143412-3d41663116d5/go.mod h1:OvpZniSE9oRPnW7+mhxljRt2RAQU+TwcnhYbqQsPwPc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -2255,15 +2253,15 @@ github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mo github.com/yeya24/promlinter v0.1.0/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/ysmood/goob v0.3.0/go.mod h1:S3lq113Y91y1UBf1wj1pFOxeahvfKkCk6mTWTWbDdWs= -github.com/ysmood/goob v0.3.1 h1:qMp5364BGS1DLJVrAqUxTF6KOFt0YDot8GC70u/0jbI= -github.com/ysmood/goob v0.3.1/go.mod h1:S3lq113Y91y1UBf1wj1pFOxeahvfKkCk6mTWTWbDdWs= +github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/got v0.15.1/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= -github.com/ysmood/got v0.19.1/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= +github.com/ysmood/got v0.23.3/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= github.com/ysmood/gotrace v0.2.2/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= -github.com/ysmood/gotrace v0.4.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= +github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= github.com/ysmood/gson v0.6.4/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= -github.com/ysmood/gson v0.7.0 h1:oQhY2FQtfy3+bgaNeqopd7NGAB6Me+UpG0n7oO4VDko= -github.com/ysmood/gson v0.7.0/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= +github.com/ysmood/gson v0.7.1 h1:zKL2MTGtynxdBdlZjyGsvEOZ7dkxaY5TH6QhAbTgz0Q= +github.com/ysmood/gson v0.7.1/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.7.0 h1:XCGdaPExyoreoQd+H5qgxM3ReNbSPFsEXpSKwbXbwQw= github.com/ysmood/leakless v0.7.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= diff --git a/pkg/blob/load.go b/pkg/blob/load.go index 9c93fccc779..c92d49c4ca3 100644 --- a/pkg/blob/load.go +++ b/pkg/blob/load.go @@ -15,6 +15,7 @@ package blob import ( + "fmt" "io" "net/http" "os" @@ -25,16 +26,32 @@ import ( func LoadFileOrURL(fileRef string) ([]byte, error) { var raw []byte var err error - if strings.HasPrefix(fileRef, "http://") || strings.HasPrefix(fileRef, "https://") { - // #nosec G107 - resp, err := http.Get(fileRef) - if err != nil { - return nil, err - } - defer resp.Body.Close() - raw, err = io.ReadAll(resp.Body) - if err != nil { - return nil, err + parts := strings.SplitAfterN(fileRef, "://", 2) + if len(parts) == 2 { + scheme := parts[0] + switch scheme { + case "http://": + fallthrough + case "https://": + // #nosec G107 + resp, err := http.Get(fileRef) + if err != nil { + return nil, err + } + defer resp.Body.Close() + raw, err = io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + case "env://": + envVar := parts[1] + value, found := os.LookupEnv(envVar) + if !found { + return nil, fmt.Errorf("loading URL: env var $%s not found", envVar) + } + raw = []byte(value) + default: + return nil, fmt.Errorf("loading URL: unrecognized scheme: %s", scheme) } } else { raw, err = os.ReadFile(filepath.Clean(fileRef)) diff --git a/pkg/blob/load_test.go b/pkg/blob/load_test.go new file mode 100644 index 00000000000..add9db7bd62 --- /dev/null +++ b/pkg/blob/load_test.go @@ -0,0 +1,99 @@ +// +// 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 blob + +import ( + "bytes" + "net/http" + "net/http/httptest" + "os" + "path" + "runtime" + "testing" +) + +func TestLoadFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows due to https://github.com/golang/go/issues/51442") + } + temp := t.TempDir() + fname := "filename.txt" + path := path.Join(temp, fname) + data := []byte("test") + defer os.Remove(path) + os.WriteFile(path, data, 0400) + + // absolute path + actual, err := LoadFileOrURL(path) + if err != nil { + t.Errorf("Reading from absolute path %s failed: %v", path, err) + } else if !bytes.Equal(actual, data) { + t.Errorf("LoadFileOrURL(absolute path) = '%s'; want '%s'", actual, data) + } + + if err = os.Chdir(temp); err != nil { + t.Fatalf("Chdir('%s'): %v", temp, err) + } + actual, err = LoadFileOrURL(fname) + if err != nil { + t.Errorf("Reading from relative path %s failed: %v", fname, err) + } else if !bytes.Equal(actual, data) { + t.Errorf("LoadFileOrURL(relative path) = '%s'; want '%s'", actual, data) + } +} + +func TestLoadURL(t *testing.T) { + data := []byte("test") + + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Write(data) + })) + defer server.Close() + + actual, err := LoadFileOrURL(server.URL) + if err != nil { + t.Errorf("Reading from HTTP failed: %v", err) + } else if !bytes.Equal(actual, data) { + t.Errorf("LoadFileOrURL(HTTP) = '%s'; want '%s'", actual, data) + } + + os.Setenv("MY_ENV_VAR", string(data)) + actual, err = LoadFileOrURL("env://MY_ENV_VAR") + if err != nil { + t.Errorf("Reading from environment failed: %v", err) + } else if !bytes.Equal(actual, data) { + t.Errorf("LoadFileOrURL(env) = '%s'; want '%s'", actual, data) + } + + os.Setenv("MY_ENV_VAR", "") + actual, err = LoadFileOrURL("env://MY_ENV_VAR") + if err != nil { + t.Errorf("Reading from environment failed: %v", err) + } else if !bytes.Equal(actual, make([]byte, 0)) { + t.Errorf("LoadFileOrURL(env) = '%s'; should be empty", actual) + } + + os.Unsetenv("MY_ENV_VAR") + _, err = LoadFileOrURL("env://MY_ENV_VAR") + if err == nil { + t.Error("LoadFileOrURL(): expected error for unset env var") + } + + _, err = LoadFileOrURL("invalid://url") + if err == nil { + t.Error("LoadFileOrURL(): expected error for invalid scheme") + } +} diff --git a/pkg/signature/keys.go b/pkg/signature/keys.go index 775be117d15..610d0c25b3a 100644 --- a/pkg/signature/keys.go +++ b/pkg/signature/keys.go @@ -19,8 +19,6 @@ import ( "crypto" "crypto/x509" "fmt" - "os" - "path/filepath" "strings" "github.com/pkg/errors" @@ -86,7 +84,7 @@ func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto. } func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, error) { - kb, err := os.ReadFile(filepath.Clean(keyPath)) + kb, err := blob.LoadFileOrURL(keyPath) if err != nil { return nil, err } @@ -169,10 +167,14 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass if strings.Contains(keyRef, "://") { sv, err := kms.Get(ctx, keyRef, crypto.SHA256) - if err != nil { + if err == nil { + return sv, nil + } + var e *kms.ProviderNotFoundError + if !errors.As(err, &e) { return nil, fmt.Errorf("kms get: %w", err) } - return sv, nil + // ProviderNotFoundError is okay; loadKey handles other URL schemes } return loadKey(keyRef, pf) diff --git a/pkg/signature/keys_test.go b/pkg/signature/keys_test.go index e95d335639e..5f1c0ac4389 100644 --- a/pkg/signature/keys_test.go +++ b/pkg/signature/keys_test.go @@ -108,6 +108,35 @@ func TestPublicKeyFromFileRef(t *testing.T) { } } +func TestPublicKeyFromEnvVar(t *testing.T) { + keys, err := cosign.GenerateKeyPair(pass("whatever")) + if err != nil { + t.Fatalf("failed to generate keypair: %v", err) + } + ctx := context.Background() + + os.Setenv("MY_ENV_VAR", string(keys.PublicBytes)) + defer os.Unsetenv("MY_ENV_VAR") + if _, err := PublicKeyFromKeyRef(ctx, "env://MY_ENV_VAR"); err != nil { + t.Fatalf("PublicKeyFromKeyRef returned error: %v", err) + } +} + +func TestSignerVerifierFromEnvVar(t *testing.T) { + passFunc := pass("whatever") + keys, err := cosign.GenerateKeyPair(passFunc) + if err != nil { + t.Fatalf("failed to generate keypair: %v", err) + } + ctx := context.Background() + + os.Setenv("MY_ENV_VAR", string(keys.PrivateBytes)) + defer os.Unsetenv("MY_ENV_VAR") + if _, err := SignerVerifierFromKeyRef(ctx, "env://MY_ENV_VAR", passFunc); err != nil { + t.Fatalf("SignerVerifierFromKeyRef returned error: %v", err) + } +} + func pass(s string) cosign.PassFunc { return func(_ bool) ([]byte, error) { return []byte(s), nil