diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..27b90443 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +ARG GO_VERSION=1.17 + +FROM golang:${GO_VERSION}-alpine as build + +RUN apk --no-cache add make +WORKDIR /src +COPY go.mod go.sum /src/ +RUN go mod download +COPY . /src/ +RUN make build + +FROM gcr.io/distroless/base:debug AS debug +COPY --from=build /src/bin/in-toto /bin/in-toto +ENTRYPOINT [ "/bin/in-toto" ] + +FROM gcr.io/distroless/base +COPY --from=build /src/bin/in-toto /bin/in-toto +ENTRYPOINT [ "/bin/in-toto" ] diff --git a/Makefile b/Makefile index a4f04127..95f77fbf 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ ORGANIZATION := example ROOT_DAYS := 3650 INTERMEDIATE_DAYS := 3650 LEAF_DAYS := 1 +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) + # Template Locations OPENSSL_TMPL := ./certs/openssl.cnf.tmpl @@ -14,12 +17,13 @@ LAYOUT_TMPL := ./certs/layout.tmpl build: modules @mkdir -p bin - @go build -o ./bin/in-toto main.go + GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 go build \ + -o ./bin/in-toto main.go modules: @go mod tidy -clean: clean-certs clean-test-files +clean: clean-certs clean-test-files spiffe-infra-down @rm -rf ./bin clean-certs: @@ -30,22 +34,22 @@ clean-test-files: @rm -rf ./untar.link @rm -rf ./.srl -test: go-test test-verify +test: go-test test-verify test-spiffe-verify go-test: @go test ./... test-sign: build generate_layout # Running test-sign - @./bin/in-toto sign -f ./test/tmp/test.layout -k ./certs/example.com.layout.key.pem -o ./test/tmp/signed.layout + cd ./test/tmp; ../../bin/in-toto sign -f ./test.layout -k ../../certs/example.com.layout.key.pem -o ./signed.layout test-record: build generate_layout # Running record start - @./bin/in-toto record start -n write-code -c ./certs/example.com.write-code.cert.pem -k ./certs/example.com.write-code.key.pem -d ./test/tmp + cd ./test/tmp; ../../bin/in-toto record start -n write-code -c ../../certs/example.com.write-code.cert.pem -k ../../certs/example.com.write-code.key.pem -d . # Record running step - @echo goodbye > ./test/tmp/foo.py + cd ./test/tmp; echo goodbye > foo.py # Running record stop - @./bin/in-toto record stop -n write-code -c ./certs/example.com.write-code.cert.pem -p ./test/tmp/foo.py -k ./certs/example.com.write-code.key.pem -d ./test/tmp + cd ./test/tmp; ../../bin/in-toto record stop -n write-code -c ../../certs/example.com.write-code.cert.pem -p foo.py -k ../../certs/example.com.write-code.key.pem -d . test-run: build generate_layout # Running write code step @@ -55,12 +59,42 @@ test-run: build generate_layout test-verify: test-sign test-run # Running test verify - @./bin/in-toto verify -l ./test/tmp/signed.layout -k ./certs/example.com.layout.cert.pem -i ./certs/example.com.intermediate.cert.pem -d ./test/tmp + cd ./test/tmp; ../../bin/in-toto verify -l ./signed.layout -k ../../certs/example.com.layout.cert.pem -i ../../certs/example.com.intermediate.cert.pem -d . + +test-spiffe-run: test-spiffe-sign + # Running write code step + docker exec -u 1000 -w /test/tmp -it intoto-runner in-toto run --spiffe-workload-api-path unix:///run/spire/sockets/agent.sock -n write-code -p foo.py -d . -- sh -c "echo hello > foo.py" + # Running package step + docker exec -u 1001 -w /test/tmp -it intoto-runner in-toto run --spiffe-workload-api-path unix:///run/spire/sockets/agent.sock -n package -m foo.py -p foo.tar.gz -d . -- tar zcvf foo.tar.gz foo.py + +test-spiffe-verify: test-spiffe-sign test-spiffe-run + # Running test verify + docker exec -it -w /test/tmp intoto-runner /bin/in-toto verify -l ./spiffe.signed.layout -k ./layout-svid.pem -d . + +test-spiffe-sign: build spiffe-test-generate-layout + docker exec -it -w /test/tmp intoto-runner /bin/in-toto sign -f ./spiffe.test.layout -k ./layout-key.pem -o ./spiffe.signed.layout + +spiffe-test-generate-layout: spiffe-infra-up + # Get key layout from the root cert + $(eval rootca := $(shell ./bin/in-toto key layout ./test/tmp/layout-bundle.pem | sed -e 's/\\n/\\\\n/g')) + cat $(LAYOUT_TMPL) | sed -e 's#{{ROOTCA}}#$(rootca)#' > ./test/tmp/spiffe.test.layout + docker-compose -f ./test-infra/docker-compose.yaml up -d intoto-runner + sleep 5 # sleep to ensure the intoto-runner is fully up and connected to spire + +spiffe-infra-up: build + @mkdir -p ./test/tmp + @chmod 777 ./test/tmp + ./test-infra/infra-up.sh + ./test-infra/mint-cert.sh layout + +spiffe-infra-down: + ./test-infra/infra-down.sh -generate_layout: leaf_certs +generate_layout: build leaf_certs @mkdir -p ./test/tmp + # get key layout from the root cert $(eval rootca := $(shell ./bin/in-toto key layout ./certs/root.cert.pem | sed -e 's/\\n/\\\\n/g')) - @cat $(LAYOUT_TMPL) | sed -e 's#{{ROOTCA}}#$(rootca)#' > ./test/tmp/test.layout + cat $(LAYOUT_TMPL) | sed -e 's#{{ROOTCA}}#$(rootca)#' > ./test/tmp/test.layout root-cert: # Generate root cert openssl conf file diff --git a/README.md b/README.md index ed67cb0f..28099a72 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ func main() { To run the demo, pull down the source code, install Go, and run `make test-verify`. This will use openssl to generate a certificate chain. +To run the demo using Spire, pull down the source code, install Go and Docker, and run `make test-spiffe-verify`. + +SPIFFE compliant Leaf certificates are generated with SVIDs corresponding to functionaries. These certificates are consumed by in-toto to sign link-meta data and the layout policy. + During the in-toto verification process, `certificate constraints` are checked to ensure the build step link meta-data was signed with the correct SVID. ## Building @@ -115,36 +119,39 @@ Usage: in-toto run [flags] Flags: - -c, --cert string Path to a PEM formatted certificate that corresponds with - the provided key. - -e, --exclude stringArray Path patterns to match paths that should not be recorded as 0 - ‘materials’ or ‘products’. Passed patterns override patterns defined - in environment variables or config files. See Config docs for details. - -h, --help help for run - -k, --key string Path to a PEM formatted private key file used to sign - the resulting link metadata. - -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing - them to the resulting link metadata. If multiple prefixes - are specified, only a single prefix can match the path of - any artifact and that is then left-stripped. All prefixes - are checked to ensure none of them are a left substring - of another. - -m, --materials stringArray Paths to files or directories, whose paths and hashes - are stored in the resulting link metadata before the - command is executed. Symlinks are followed. - -d, --metadata-directory string Directory to store link metadata (default "./") - -n, --name string Name used to associate the resulting link metadata - with the corresponding step defined in an in-toto layout. - --normalize-line-endings Enable line normalization in order to support different - operating systems. It is done by replacing all line separators - with a new line character. - -p, --products stringArray Paths to files or directories, whose paths and hashes - are stored in the resulting link metadata after the - command is executed. Symlinks are followed. - -r, --run-dir string runDir specifies the working directory of the command. - If runDir is the empty string, the command will run in the - calling process's current directory. The runDir directory must - exist, be writable, and not be a symlink. + -c, --cert string Path to a PEM formatted certificate that corresponds with + the provided key. + -e, --exclude stringArray path patterns to match paths that should not be recorded as 0 + ‘materials’ or ‘products’. Passed patterns override patterns defined + in environment variables or config files. See Config docs for details. + -h, --help help for run + -k, --key string Path to a PEM formatted private key file used to sign + the resulting link metadata. (passing one of '--key' + or '--gpg' is required) + -l, --lstrip-paths stringArray path prefixes used to left-strip artifact paths before storing + them to the resulting link metadata. If multiple prefixes + are specified, only a single prefix can match the path of + any artifact and that is then left-stripped. All prefixes + are checked to ensure none of them are a left substring + of another. + -m, --materials stringArray Paths to files or directories, whose paths and hashes + are stored in the resulting link metadata before the + command is executed. Symlinks are followed. + -n, --name string Name used to associate the resulting link metadata + with the corresponding step defined in an in-toto + layout. + --normalize-line-endings Enable line normalization in order to support different + operating systems. It is done by replacing all line separators + with a new line character. + -d, --metadata-directory string directory to store link metadata (default "./") + -p, --products stringArray Paths to files or directories, whose paths and hashes + are stored in the resulting link metadata after the + command is executed. Symlinks are followed. + -r, --run-dir string runDir specifies the working directory of the command. + If runDir is the empty string, the command will run in the + calling process's current directory. The runDir directory must + exist, be writable, and not be a symlink. + --spiffe-workload-api-path string uds path for spiffe workload api ``` ### sign @@ -211,29 +218,29 @@ passed materials and signs it with the passed functionary’s key. stop Records and adds the paths and hashes of the passed products to the link metadata file and updates the signature. Flags: - -c, --cert string Path to a PEM formatted certificate that corresponds - with the provided key. - -e, --exclude stringArray Path patterns to match paths that should not be recorded as - ‘materials’ or ‘products’. Passed patterns override patterns defined - in environment variables or config files. See Config docs for details. - -h, --help help for record - -k, --key string Path to a private key file to sign the resulting link metadata. - The keyid prefix is used as an infix for the link metadata filename, - i.e. ‘..link’. See ‘–key-type’ for available - formats. Passing one of ‘–key’ or ‘–gpg’ is required. - -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing - them to the resulting link metadata. If multiple prefixes - are specified, only a single prefix can match the path of - any artifact and that is then left-stripped. All prefixes - are checked to ensure none of them are a left substring - of another. - -d, --metadata-directory string Directory to store link metadata (default "./") - -n, --name string Name for the resulting link metadata file. - It is also used to associate the link with a step defined - in an in-toto layout. - --normalize-line-endings Enable line normalization in order to support different - operating systems. It is done by replacing all line separators - with a new line character. + -c, --cert string Path to a PEM formatted certificate that corresponds with the provided key. + -e, --exclude stringArray Path patterns to match paths that should not be recorded as + ‘materials’ or ‘products’. Passed patterns override patterns defined + in environment variables or config files. See Config docs for details. + -h, --help help for record + -k, --key string Path to a private key file to sign the resulting link metadata. + The keyid prefix is used as an infix for the link metadata filename, + i.e. ‘..link’. See ‘–key-type’ for available + formats. Passing one of ‘–key’ or ‘–gpg’ is required. + -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing + them to the resulting link metadata. If multiple prefixes + are specified, only a single prefix can match the path of + any artifact and that is then left-stripped. All prefixes + are checked to ensure none of them are a left substring + of another. + -d, --metadata-directory string directory to store link metadata (default "./") + -n, --name string name for the resulting link metadata file. + It is also used to associate the link with a step defined + in an in-toto layout. + --normalize-line-endings Enable line normalization in order to support different + operating systems. It is done by replacing all line separators + with a new line character. + --spiffe-workload-api-path string uds path for spiffe workload api Use "in-toto record [command] --help" for more information about a command. ``` diff --git a/certs/layout.tmpl b/certs/layout.tmpl index 24c96f15..5579115c 100644 --- a/certs/layout.tmpl +++ b/certs/layout.tmpl @@ -38,7 +38,7 @@ "run": [ "tar", "xfz", - "./test/tmp/foo.tar.gz" + "foo.tar.gz" ] } ], @@ -53,7 +53,7 @@ "_type": "step", "cert_constraints": [ { - "common_name": "write-code.example.com", + "common_name": "*", "dns_names": [ "" ], @@ -71,7 +71,7 @@ ] } ], - "expected_command": ["/bin/sh -c echo hello > ./test/tmp/foo.py"], + "expected_command": ["sh -c echo hello > foo.py"], "expected_materials": [], "expected_products": [ [ @@ -87,7 +87,7 @@ "_type": "step", "cert_constraints": [ { - "common_name": "package.example.com", + "common_name": "*", "dns_names": [ "" ], @@ -108,8 +108,8 @@ "expected_command": [ "tar", "zcvf", - "./test/tmp/foo.tar.gz", - "./test/tmp/foo.py" + "foo.tar.gz", + "foo.py" ], "expected_materials": [ [ diff --git a/cmd/record.go b/cmd/record.go index f5942e2f..28dd1ebf 100644 --- a/cmd/record.go +++ b/cmd/record.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "os" "path/filepath" intoto "github.com/in-toto/in-toto-golang/in_toto" @@ -23,7 +22,7 @@ var recordCmd = &cobra.Command{ evidence for supply chain steps that cannot be carried out by a single command (for which ‘in-toto-run’ should be used). It returns a non-zero value on failure and zero otherwise.`, - PersistentPreRunE: recordPreRun, + PersistentPreRunE: getKeyCert, } var recordStartCmd = &cobra.Command{ @@ -111,6 +110,22 @@ of another.`, in environment variables or config files. See Config docs for details.`, ) + recordCmd.PersistentFlags().StringVar( + &spiffeUDS, + "spiffe-workload-api-path", + "", + "UDS path for SPIFFE workload API", + ) + + recordCmd.PersistentFlags().BoolVar( + &lineNormalization, + "normalize-line-endings", + false, + `Enable line normalization in order to support different +operating systems. It is done by replacing all line separators +with a new line character.`, + ) + recordCmd.MarkPersistentFlagRequired("name") // Record Start Command @@ -138,46 +153,6 @@ command is executed. Symlinks are followed.`, are stored in the resulting link metadata after the command is executed. Symlinks are followed.`, ) - - recordCmd.Flags().BoolVar( - &lineNormalization, - "normalize-line-endings", - false, - `Enable line normalization in order to support different -operating systems. It is done by replacing all line separators -with a new line character.`, - ) -} - -func recordPreRun(cmd *cobra.Command, args []string) error { - key = intoto.Key{} - cert = intoto.Key{} - - if keyPath == "" && certPath == "" { - return fmt.Errorf("key or cert must be provided") - } - - if len(keyPath) > 0 { - if _, err := os.Stat(keyPath); err == nil { - if err := key.LoadKeyDefaults(keyPath); err != nil { - return fmt.Errorf("invalid key at %s: %w", keyPath, err) - } - } else { - return fmt.Errorf("key not found at %s: %w", keyPath, err) - } - } - - if len(certPath) > 0 { - if _, err := os.Stat(certPath); err == nil { - if err := cert.LoadKeyDefaults(certPath); err != nil { - return fmt.Errorf("invalid cert at %s: %w", certPath, err) - } - key.KeyVal.Certificate = cert.KeyVal.Certificate - } else { - return fmt.Errorf("cert not found at %s: %w", certPath, err) - } - } - return nil } func recordStart(cmd *cobra.Command, args []string) error { diff --git a/cmd/root.go b/cmd/root.go index ed949b93..02391f08 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,14 +1,19 @@ package cmd import ( + "context" + "encoding/pem" "fmt" "os" + "path/filepath" intoto "github.com/in-toto/in-toto-golang/in_toto" + "github.com/in-toto/in-toto-golang/internal/spiffe" "github.com/spf13/cobra" ) var ( + spiffeUDS string layoutPath string keyPath string certPath string @@ -29,6 +34,81 @@ var rootCmd = &cobra.Command{ DisableAutoGenTag: true, } +func loadKeyFromSpireSocket() error { + ctx := context.Background() + var err error + spireClient, err := spiffe.NewClient(ctx, spiffeUDS) + if err != nil { + return fmt.Errorf("failed to create spire client: %w", err) + } + + svidDetails, err := spiffe.GetSVID(ctx, spireClient) + if err != nil { + return fmt.Errorf("failed to get spiffe x.509 SVID: %w", err) + } + + key, err = svidDetails.InTotoKey() + if err != nil { + return fmt.Errorf("failed to convert svid to in-toto key: %w", err) + } + + // Write out any intermediates necessary to build the trust back + // to the root for use during verification. + for i, c := range svidDetails.Intermediates { + certFileName := fmt.Sprintf("%v-intermediate-%v.cert.pem", stepName, i) + certFile := filepath.Join(outDir, certFileName) + certOut, err := os.Create(certFile) + if err != nil { + return fmt.Errorf("failed to write spiffe intermediate cert to %s: %w", certFile, err) + } + + defer certOut.Close() + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: c.Raw}); err != nil { + return fmt.Errorf("failed to encode spiffe intermediate cert: %w", err) + } + } + + return nil +} + +func loadKeyFromDisk() error { + key = intoto.Key{} + cert = intoto.Key{} + + if keyPath == "" && certPath == "" { + return fmt.Errorf("key or cert must be provided") + } + + if len(keyPath) > 0 { + if _, err := os.Stat(keyPath); err == nil { + if err := key.LoadKeyDefaults(keyPath); err != nil { + return fmt.Errorf("invalid key at %s: %w", keyPath, err) + } + } else { + return fmt.Errorf("key not found at %s: %w", keyPath, err) + } + } + + if len(certPath) > 0 { + if _, err := os.Stat(certPath); err == nil { + if err := cert.LoadKeyDefaults(certPath); err != nil { + return fmt.Errorf("invalid cert at %s: %w", certPath, err) + } + key.KeyVal.Certificate = cert.KeyVal.Certificate + } else { + return fmt.Errorf("cert not found at %s: %w", certPath, err) + } + } + return nil +} + +func getKeyCert(cmd *cobra.Command, args []string) error { + if spiffeUDS != "" { + return loadKeyFromSpireSocket() + } + return loadKeyFromDisk() +} + // Execute runs the root command func Execute() { if err := rootCmd.Execute(); err != nil { diff --git a/cmd/run.go b/cmd/run.go index 832518de..7599a4c2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "os" "path/filepath" intoto "github.com/in-toto/in-toto-golang/in_toto" @@ -25,7 +24,7 @@ execution) and stores them together with other information (executed command, return value, stdout, stderr, ...) to a link metadata file, which is signed with the passed key. Returns nonzero value on failure and zero otherwise.`, Args: cobra.MinimumNArgs(1), - PreRunE: runPreRun, + PreRunE: getKeyCert, RunE: run, } @@ -131,37 +130,14 @@ in environment variables or config files. See Config docs for details.`, operating systems. It is done by replacing all line separators with a new line character.`, ) -} - -func runPreRun(cmd *cobra.Command, args []string) error { - key = intoto.Key{} - cert = intoto.Key{} - if keyPath == "" && certPath == "" { - return fmt.Errorf("key or cert must be provided") - } - - if len(keyPath) > 0 { - if _, err := os.Stat(keyPath); err == nil { - if err := key.LoadKeyDefaults(keyPath); err != nil { - return fmt.Errorf("invalid key at %s: %w", keyPath, err) - } - } else { - return fmt.Errorf("key not found at %s: %w", keyPath, err) - } - } + runCmd.Flags().StringVar( + &spiffeUDS, + "spiffe-workload-api-path", + "", + "UDS path for SPIFFE workload API", + ) - if len(certPath) > 0 { - if _, err := os.Stat(certPath); err == nil { - if err := cert.LoadKeyDefaults(certPath); err != nil { - return fmt.Errorf("invalid cert at %s: %w", certPath, err) - } - key.KeyVal.Certificate = cert.KeyVal.Certificate - } else { - return fmt.Errorf("cert not found at %s: %w", certPath, err) - } - } - return nil } func run(cmd *cobra.Command, args []string) error { diff --git a/doc/in-toto_record.md b/doc/in-toto_record.md index 36ee21b8..ebba196b 100644 --- a/doc/in-toto_record.md +++ b/doc/in-toto_record.md @@ -13,29 +13,30 @@ failure and zero otherwise. ### Options ``` - -c, --cert string Path to a PEM formatted certificate that corresponds - with the provided key. - -e, --exclude stringArray Path patterns to match paths that should not be recorded as - ‘materials’ or ‘products’. Passed patterns override patterns defined - in environment variables or config files. See Config docs for details. - -h, --help help for record - -k, --key string Path to a private key file to sign the resulting link metadata. - The keyid prefix is used as an infix for the link metadata filename, - i.e. ‘..link’. See ‘–key-type’ for available - formats. Passing one of ‘–key’ or ‘–gpg’ is required. - -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing - them to the resulting link metadata. If multiple prefixes - are specified, only a single prefix can match the path of - any artifact and that is then left-stripped. All prefixes - are checked to ensure none of them are a left substring - of another. - -d, --metadata-directory string Directory to store link metadata (default "./") - -n, --name string Name for the resulting link metadata file. - It is also used to associate the link with a step defined - in an in-toto layout. - --normalize-line-endings Enable line normalization in order to support different - operating systems. It is done by replacing all line separators - with a new line character. + -c, --cert string Path to a PEM formatted certificate that corresponds + with the provided key. + -e, --exclude stringArray Path patterns to match paths that should not be recorded as + ‘materials’ or ‘products’. Passed patterns override patterns defined + in environment variables or config files. See Config docs for details. + -h, --help help for record + -k, --key string Path to a private key file to sign the resulting link metadata. + The keyid prefix is used as an infix for the link metadata filename, + i.e. ‘..link’. See ‘–key-type’ for available + formats. Passing one of ‘–key’ or ‘–gpg’ is required. + -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing + them to the resulting link metadata. If multiple prefixes + are specified, only a single prefix can match the path of + any artifact and that is then left-stripped. All prefixes + are checked to ensure none of them are a left substring + of another. + -d, --metadata-directory string Directory to store link metadata (default "./") + -n, --name string Name for the resulting link metadata file. + It is also used to associate the link with a step defined + in an in-toto layout. + --normalize-line-endings Enable line normalization in order to support different + operating systems. It is done by replacing all line separators + with a new line character. + --spiffe-workload-api-path string UDS path for SPIFFE workload API ``` ### SEE ALSO diff --git a/doc/in-toto_record_start.md b/doc/in-toto_record_start.md index 9c6e658f..8fb8464c 100644 --- a/doc/in-toto_record_start.md +++ b/doc/in-toto_record_start.md @@ -25,25 +25,29 @@ in-toto record start [flags] ### Options inherited from parent commands ``` - -c, --cert string Path to a PEM formatted certificate that corresponds - with the provided key. - -e, --exclude stringArray Path patterns to match paths that should not be recorded as - ‘materials’ or ‘products’. Passed patterns override patterns defined - in environment variables or config files. See Config docs for details. - -k, --key string Path to a private key file to sign the resulting link metadata. - The keyid prefix is used as an infix for the link metadata filename, - i.e. ‘..link’. See ‘–key-type’ for available - formats. Passing one of ‘–key’ or ‘–gpg’ is required. - -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing - them to the resulting link metadata. If multiple prefixes - are specified, only a single prefix can match the path of - any artifact and that is then left-stripped. All prefixes - are checked to ensure none of them are a left substring - of another. - -d, --metadata-directory string Directory to store link metadata (default "./") - -n, --name string Name for the resulting link metadata file. - It is also used to associate the link with a step defined - in an in-toto layout. + -c, --cert string Path to a PEM formatted certificate that corresponds + with the provided key. + -e, --exclude stringArray Path patterns to match paths that should not be recorded as + ‘materials’ or ‘products’. Passed patterns override patterns defined + in environment variables or config files. See Config docs for details. + -k, --key string Path to a private key file to sign the resulting link metadata. + The keyid prefix is used as an infix for the link metadata filename, + i.e. ‘..link’. See ‘–key-type’ for available + formats. Passing one of ‘–key’ or ‘–gpg’ is required. + -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing + them to the resulting link metadata. If multiple prefixes + are specified, only a single prefix can match the path of + any artifact and that is then left-stripped. All prefixes + are checked to ensure none of them are a left substring + of another. + -d, --metadata-directory string Directory to store link metadata (default "./") + -n, --name string Name for the resulting link metadata file. + It is also used to associate the link with a step defined + in an in-toto layout. + --normalize-line-endings Enable line normalization in order to support different + operating systems. It is done by replacing all line separators + with a new line character. + --spiffe-workload-api-path string UDS path for SPIFFE workload API ``` ### SEE ALSO diff --git a/doc/in-toto_record_stop.md b/doc/in-toto_record_stop.md index a2324429..666e3637 100644 --- a/doc/in-toto_record_stop.md +++ b/doc/in-toto_record_stop.md @@ -26,25 +26,29 @@ in-toto record stop [flags] ### Options inherited from parent commands ``` - -c, --cert string Path to a PEM formatted certificate that corresponds - with the provided key. - -e, --exclude stringArray Path patterns to match paths that should not be recorded as - ‘materials’ or ‘products’. Passed patterns override patterns defined - in environment variables or config files. See Config docs for details. - -k, --key string Path to a private key file to sign the resulting link metadata. - The keyid prefix is used as an infix for the link metadata filename, - i.e. ‘..link’. See ‘–key-type’ for available - formats. Passing one of ‘–key’ or ‘–gpg’ is required. - -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing - them to the resulting link metadata. If multiple prefixes - are specified, only a single prefix can match the path of - any artifact and that is then left-stripped. All prefixes - are checked to ensure none of them are a left substring - of another. - -d, --metadata-directory string Directory to store link metadata (default "./") - -n, --name string Name for the resulting link metadata file. - It is also used to associate the link with a step defined - in an in-toto layout. + -c, --cert string Path to a PEM formatted certificate that corresponds + with the provided key. + -e, --exclude stringArray Path patterns to match paths that should not be recorded as + ‘materials’ or ‘products’. Passed patterns override patterns defined + in environment variables or config files. See Config docs for details. + -k, --key string Path to a private key file to sign the resulting link metadata. + The keyid prefix is used as an infix for the link metadata filename, + i.e. ‘..link’. See ‘–key-type’ for available + formats. Passing one of ‘–key’ or ‘–gpg’ is required. + -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing + them to the resulting link metadata. If multiple prefixes + are specified, only a single prefix can match the path of + any artifact and that is then left-stripped. All prefixes + are checked to ensure none of them are a left substring + of another. + -d, --metadata-directory string Directory to store link metadata (default "./") + -n, --name string Name for the resulting link metadata file. + It is also used to associate the link with a step defined + in an in-toto layout. + --normalize-line-endings Enable line normalization in order to support different + operating systems. It is done by replacing all line separators + with a new line character. + --spiffe-workload-api-path string UDS path for SPIFFE workload API ``` ### SEE ALSO diff --git a/doc/in-toto_run.md b/doc/in-toto_run.md index b8141f8e..f54f3765 100644 --- a/doc/in-toto_run.md +++ b/doc/in-toto_run.md @@ -17,36 +17,37 @@ in-toto run [flags] ### Options ``` - -c, --cert string Path to a PEM formatted certificate that corresponds with - the provided key. - -e, --exclude stringArray Path patterns to match paths that should not be recorded as 0 - ‘materials’ or ‘products’. Passed patterns override patterns defined - in environment variables or config files. See Config docs for details. - -h, --help help for run - -k, --key string Path to a PEM formatted private key file used to sign - the resulting link metadata. - -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing - them to the resulting link metadata. If multiple prefixes - are specified, only a single prefix can match the path of - any artifact and that is then left-stripped. All prefixes - are checked to ensure none of them are a left substring - of another. - -m, --materials stringArray Paths to files or directories, whose paths and hashes - are stored in the resulting link metadata before the - command is executed. Symlinks are followed. - -d, --metadata-directory string Directory to store link metadata (default "./") - -n, --name string Name used to associate the resulting link metadata - with the corresponding step defined in an in-toto layout. - --normalize-line-endings Enable line normalization in order to support different - operating systems. It is done by replacing all line separators - with a new line character. - -p, --products stringArray Paths to files or directories, whose paths and hashes - are stored in the resulting link metadata after the - command is executed. Symlinks are followed. - -r, --run-dir string runDir specifies the working directory of the command. - If runDir is the empty string, the command will run in the - calling process's current directory. The runDir directory must - exist, be writable, and not be a symlink. + -c, --cert string Path to a PEM formatted certificate that corresponds with + the provided key. + -e, --exclude stringArray Path patterns to match paths that should not be recorded as 0 + ‘materials’ or ‘products’. Passed patterns override patterns defined + in environment variables or config files. See Config docs for details. + -h, --help help for run + -k, --key string Path to a PEM formatted private key file used to sign + the resulting link metadata. + -l, --lstrip-paths stringArray Path prefixes used to left-strip artifact paths before storing + them to the resulting link metadata. If multiple prefixes + are specified, only a single prefix can match the path of + any artifact and that is then left-stripped. All prefixes + are checked to ensure none of them are a left substring + of another. + -m, --materials stringArray Paths to files or directories, whose paths and hashes + are stored in the resulting link metadata before the + command is executed. Symlinks are followed. + -d, --metadata-directory string Directory to store link metadata (default "./") + -n, --name string Name used to associate the resulting link metadata + with the corresponding step defined in an in-toto layout. + --normalize-line-endings Enable line normalization in order to support different + operating systems. It is done by replacing all line separators + with a new line character. + -p, --products stringArray Paths to files or directories, whose paths and hashes + are stored in the resulting link metadata after the + command is executed. Symlinks are followed. + -r, --run-dir string runDir specifies the working directory of the command. + If runDir is the empty string, the command will run in the + calling process's current directory. The runDir directory must + exist, be writable, and not be a symlink. + --spiffe-workload-api-path string UDS path for SPIFFE workload API ``` ### SEE ALSO diff --git a/go.mod b/go.mod index 4a6789e6..311e65be 100644 --- a/go.mod +++ b/go.mod @@ -7,18 +7,28 @@ require ( github.com/secure-systems-lab/go-securesystemslib v0.4.0 github.com/shibumi/go-pathspec v1.3.0 github.com/spf13/cobra v1.5.0 + github.com/spiffe/go-spiffe/v2 v2.1.1 github.com/stretchr/testify v1.8.0 golang.org/x/sys v0.0.0-20211210111614-af8b64212486 + google.golang.org/grpc v1.49.0 ) require ( + github.com/Microsoft/go-winio v0.5.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/zeebo/errs v1.2.2 // indirect golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/square/go-jose.v2 v2.4.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6825cead..434a3c1e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,18 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= @@ -5,44 +20,165 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE= github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.1.1 h1:RT9kM8MZLZIsPTH+HKQEP5yaAk3yd/VBzlINaRjXs8k= +github.com/spiffe/go-spiffe/v2 v2.1.1/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= +github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/spiffe/spiffe.go b/internal/spiffe/spiffe.go new file mode 100644 index 00000000..d23f0863 --- /dev/null +++ b/internal/spiffe/spiffe.go @@ -0,0 +1,93 @@ +package spiffe + +import ( + "bytes" + "context" + "crypto" + "crypto/x509" + "encoding/pem" + "fmt" + + intoto "github.com/in-toto/in-toto-golang/in_toto" + "github.com/spiffe/go-spiffe/v2/workloadapi" +) + +/* +SVIDDetails captures the Private Key, Root and Intermediate Certificate +from the SVID provided by spire for the workload. +*/ +type SVIDDetails struct { + PrivateKey crypto.Signer + Certificate *x509.Certificate + Intermediates []*x509.Certificate +} + +/* +SVIDFetcher uses the context to connect to the spire and get the SVID associated with +the workload. +*/ +type SVIDFetcher interface { + FetchX509Context(ctx context.Context) (*workloadapi.X509Context, error) + Close() error +} + +/* +NewClient takes the context and the provided spire agent socket path in order to initialize +the workload API. +*/ +func NewClient(ctx context.Context, socketPath string) (SVIDFetcher, error) { + return workloadapi.New(ctx, workloadapi.WithAddr(socketPath)) +} + +/* +GetSVID attempts to request an SVID from the provided SPIRE Workload API socket. +If attestation succeeds and an SVID is acquired the resulting X509 key & +certificate pair will be returned as well as any intermediate certificates +needed to establish trust to trust domain's root. +*/ +func GetSVID(ctx context.Context, client SVIDFetcher) (SVIDDetails, error) { + s := SVIDDetails{} + svidContext, err := client.FetchX509Context(ctx) + if err != nil { + return s, fmt.Errorf("error fetching spiffe x.509 context: %w", err) + } + + svid := svidContext.DefaultSVID() + if len(svid.Certificates) <= 0 { + return s, fmt.Errorf("no certificates in svid") + } + + if svid.PrivateKey == nil { + return s, fmt.Errorf("svid has no key") + } + + s.PrivateKey = svid.PrivateKey + s.Certificate = svid.Certificates[0] + s.Intermediates = svid.Certificates[1:] + return s, nil +} + +/* +InTotoKey uses the private key and certificate obtained from Spire to initialize +intoto.key to be used for signing. +*/ +func (s SVIDDetails) InTotoKey() (intoto.Key, error) { + key := intoto.Key{} + keyBytes, err := x509.MarshalPKCS8PrivateKey(s.PrivateKey) + if err != nil { + return key, fmt.Errorf("failed to marshal svid key: %w", err) + } + + keyPemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: keyBytes, + }) + + err = key.LoadKeyReaderDefaults(bytes.NewReader(keyPemBytes)) + if err != nil { + return key, fmt.Errorf("failed to load key from spire: %w", err) + } + + key.KeyVal.Certificate = string(pem.EncodeToMemory(&pem.Block{Bytes: s.Certificate.Raw, Type: "CERTIFICATE"})) + return key, nil +} diff --git a/internal/spiffe/spiffe_test.go b/internal/spiffe/spiffe_test.go new file mode 100644 index 00000000..3dd867a4 --- /dev/null +++ b/internal/spiffe/spiffe_test.go @@ -0,0 +1,159 @@ +package spiffe + +import ( + "context" + "crypto/x509" + "encoding/pem" + "strings" + "testing" + + intoto "github.com/in-toto/in-toto-golang/in_toto" + "github.com/in-toto/in-toto-golang/internal/test" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/go-spiffe/v2/svid/x509svid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + td = spiffeid.RequireTrustDomainFromString("example.org") + fooID = spiffeid.RequireFromPath(td, "/foo") +) + +func assertX509SVID(tb testing.TB, sd SVIDDetails, spiffeID spiffeid.ID, certificates []*x509.Certificate, intermediates []*x509.Certificate) { + assert.NotEmpty(tb, spiffeID) + assert.Equal(tb, certificates[0], sd.Certificate) + assert.Equal(tb, intermediates, sd.Intermediates) + assert.NotEmpty(tb, sd.PrivateKey) +} + +func assertInTotoKey(tb testing.TB, key intoto.Key, svid *x509svid.SVID) { + assert.NotNil(tb, key.KeyID, "keyID is empty.") + assert.Equal(tb, []string{"sha256", "sha512"}, key.KeyIDHashAlgorithms) + assert.Equal(tb, "ecdsa", key.KeyType) + assert.Equal(tb, "ecdsa-sha2-nistp256", key.Scheme) + cerBytes, keyBytes, _ := svid.Marshal() + keyData, _ := pem.Decode(keyBytes) + certData, _ := pem.Decode(cerBytes) + assert.Equal(tb, strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{Bytes: keyData.Bytes, Type: "PRIVATE KEY"}))), key.KeyVal.Private) + privKey, _ := x509.ParseCertificate(certData.Bytes) + pubKeyBytes, _ := x509.MarshalPKIXPublicKey(privKey.PublicKey) + assert.Equal(tb, strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{Bytes: pubKeyBytes, Type: "PUBLIC KEY"}))), key.KeyVal.Public) + assert.Equal(tb, string(pem.EncodeToMemory(&pem.Block{Bytes: svid.Certificates[0].Raw, Type: "CERTIFICATE"})), key.KeyVal.Certificate) + +} + +func makeX509SVIDs(ca *test.CA, ids ...spiffeid.ID) []*x509svid.SVID { + svids := []*x509svid.SVID{} + for _, id := range ids { + svids = append(svids, ca.CreateX509SVID(id)) + } + return svids +} + +func getSVIDs(t *testing.T, badInput bool) *test.X509SVIDResponse { + ca := test.NewCA(t, td) + var svids []*x509svid.SVID + if badInput { + svids = makeX509SVIDsNoPrivateKey(ca, fooID) + } else { + svids = makeX509SVIDs(ca, fooID) + } + + resp := &test.X509SVIDResponse{ + Bundle: ca.X509Bundle(), + SVIDs: svids, + } + return resp +} + +func makeX509SVIDsNoPrivateKey(ca *test.CA, ids ...spiffeid.ID) []*x509svid.SVID { + svids := []*x509svid.SVID{} + for _, id := range ids { + svids = append(svids, ca.CreateX509SVIDNoPrivateKey(id)) + } + return svids +} + +func TestNewClient(t *testing.T) { + + wl := test.NewWorkloadAPI(t) + defer wl.Stop() + spireClient, err := NewClient(context.Background(), wl.Addr()) + require.NoError(t, err) + defer spireClient.Close() + assert.Nil(t, err, "Unexpected error!") + assert.NotNil(t, spireClient, "Unexpected error getting client") +} + +func TestGetSVIDNoPrivateKey(t *testing.T) { + + wl := test.NewWorkloadAPI(t) + defer wl.Stop() + spireClient, err := NewClient(context.Background(), wl.Addr()) + require.NoError(t, err) + defer spireClient.Close() + resp := getSVIDs(t, true) + wl.SetX509SVIDResponse(resp) + + svidDetail, err := GetSVID(context.Background(), spireClient) + assert.Equal(t, SVIDDetails{PrivateKey: nil, Certificate: nil, Intermediates: nil}, svidDetail) + assert.Error(t, err) +} + +func TestGetSVID(t *testing.T) { + wl := test.NewWorkloadAPI(t) + defer wl.Stop() + spireClient, err := NewClient(context.Background(), wl.Addr()) + require.NoError(t, err) + defer spireClient.Close() + + resp := getSVIDs(t, false) + wl.SetX509SVIDResponse(resp) + + svidDetail, err := GetSVID(context.Background(), spireClient) + require.NoError(t, err) + assertX509SVID(t, svidDetail, fooID, resp.SVIDs[0].Certificates, resp.SVIDs[0].Certificates[1:]) +} + +func TestSVIDDetails_IntotoKey(t *testing.T) { + wl := test.NewWorkloadAPI(t) + defer wl.Stop() + spireClient, err := NewClient(context.Background(), wl.Addr()) + require.NoError(t, err) + defer spireClient.Close() + + resp := getSVIDs(t, false) + wl.SetX509SVIDResponse(resp) + + svidDetail, err := GetSVID(context.Background(), spireClient) + + require.NoError(t, err) + + key, err := svidDetail.InTotoKey() + assert.Nil(t, err, "Unexpected error!") + assertInTotoKey(t, key, resp.SVIDs[0]) +} + +func TestSVIDDetails_BadIntotoKey(t *testing.T) { + wl := test.NewWorkloadAPI(t) + defer wl.Stop() + spireClient, err := NewClient(context.Background(), wl.Addr()) + require.NoError(t, err) + defer spireClient.Close() + + resp := getSVIDs(t, false) + wl.SetX509SVIDResponse(resp) + + svidDetail, err := GetSVID(context.Background(), spireClient) + + require.NoError(t, err) + + svidDetail.PrivateKey = nil + + key, err := svidDetail.InTotoKey() + assert.Equal(t, intoto.Key{KeyID: "", KeyIDHashAlgorithms: nil, KeyType: "", + Scheme: "", KeyVal: intoto.KeyVal{Private: "", + Public: "", Certificate: ""}}, key) + assert.Error(t, err) +} diff --git a/internal/test/ca.go b/internal/test/ca.go new file mode 100644 index 00000000..fc1c9bd2 --- /dev/null +++ b/internal/test/ca.go @@ -0,0 +1,243 @@ +package test + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "net/url" + "testing" + "time" + + "github.com/spiffe/go-spiffe/v2/bundle/spiffebundle" + "github.com/spiffe/go-spiffe/v2/bundle/x509bundle" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/go-spiffe/v2/svid/x509svid" + "github.com/stretchr/testify/require" +) + +type CA struct { + tb testing.TB + td spiffeid.TrustDomain + parent *CA + cert *x509.Certificate + key crypto.Signer + jwtKey crypto.Signer + jwtKid string +} + +type CertificateOption interface { + apply(*x509.Certificate) +} + +type certificateOption func(*x509.Certificate) + +func (co certificateOption) apply(c *x509.Certificate) { + co(c) +} + +// NewEC256Key returns an ECDSA key over the P256 curve +func NewEC256Key(tb testing.TB) *ecdsa.PrivateKey { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(tb, err) + return key +} + +// NewKeyID returns a random id useful for identifying keys +func NewKeyID(tb testing.TB) string { + choices := make([]byte, 32) + _, err := rand.Read(choices) + require.NoError(tb, err) + return keyIDFromBytes(choices) +} + +func keyIDFromBytes(choices []byte) string { + const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + buf := new(bytes.Buffer) + for _, choice := range choices { + buf.WriteByte(alphabet[int(choice)%len(alphabet)]) + } + return buf.String() +} + +func NewCA(tb testing.TB, td spiffeid.TrustDomain) *CA { + cert, key := CreateCACertificate(tb, nil, nil) + return &CA{ + tb: tb, + td: td, + cert: cert, + key: key, + jwtKey: NewEC256Key(tb), + jwtKid: NewKeyID(tb), + } +} + +func (ca *CA) ChildCA(options ...CertificateOption) *CA { + cert, key := CreateCACertificate(ca.tb, ca.cert, ca.key, options...) + return &CA{ + tb: ca.tb, + parent: ca, + cert: cert, + key: key, + jwtKey: NewEC256Key(ca.tb), + jwtKid: NewKeyID(ca.tb), + } +} + +func (ca *CA) CreateX509SVID(id spiffeid.ID, options ...CertificateOption) *x509svid.SVID { + cert, key := CreateX509SVID(ca.tb, ca.cert, ca.key, id, options...) + return &x509svid.SVID{ + ID: id, + Certificates: append([]*x509.Certificate{cert}, ca.chain(false)...), + PrivateKey: key, + } +} + +func (ca *CA) CreateX509SVIDNoPrivateKey(id spiffeid.ID, options ...CertificateOption) *x509svid.SVID { + cert, _ := CreateX509SVID(ca.tb, ca.cert, ca.key, id, options...) + return &x509svid.SVID{ + ID: id, + Certificates: append([]*x509.Certificate{cert}, ca.chain(false)...), + } +} + +func (ca *CA) CreateX509Certificate(options ...CertificateOption) ([]*x509.Certificate, crypto.Signer) { + cert, key := CreateX509Certificate(ca.tb, ca.cert, ca.key, options...) + return append([]*x509.Certificate{cert}, ca.chain(false)...), key +} + +func (ca *CA) X509Authorities() []*x509.Certificate { + root := ca + for root.parent != nil { + root = root.parent + } + return []*x509.Certificate{root.cert} +} + +func (ca *CA) Bundle() *spiffebundle.Bundle { + bundle := spiffebundle.New(ca.td) + bundle.SetX509Authorities(ca.X509Authorities()) + return bundle +} + +func (ca *CA) X509Bundle() *x509bundle.Bundle { + return x509bundle.FromX509Authorities(ca.td, ca.X509Authorities()) +} + +func CreateCACertificate(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, options ...CertificateOption) (*x509.Certificate, crypto.Signer) { + now := time.Now() + serial := NewSerial(tb) + key := NewEC256Key(tb) + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: fmt.Sprintf("CA %x", serial), + }, + BasicConstraintsValid: true, + IsCA: true, + NotBefore: now, + NotAfter: now.Add(time.Hour), + } + + applyOptions(tmpl, options...) + + if parent == nil { + parent = tmpl + parentKey = key + } + return CreateCertificate(tb, tmpl, parent, key.Public(), parentKey), key +} + +func CreateX509Certificate(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, options ...CertificateOption) (*x509.Certificate, crypto.Signer) { + now := time.Now() + serial := NewSerial(tb) + key := NewEC256Key(tb) + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: fmt.Sprintf("X509-Certificate %x", serial), + }, + NotBefore: now, + NotAfter: now.Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + } + + applyOptions(tmpl, options...) + + return CreateCertificate(tb, tmpl, parent, key.Public(), parentKey), key +} + +func CreateX509SVID(tb testing.TB, parent *x509.Certificate, parentKey crypto.Signer, id spiffeid.ID, options ...CertificateOption) (*x509.Certificate, crypto.Signer) { + serial := NewSerial(tb) + options = append(options, + WithSerial(serial), + WithKeyUsage(x509.KeyUsageDigitalSignature), + WithSubject(pkix.Name{ + CommonName: fmt.Sprintf("X509-SVID %x", serial), + }), + WithURIs(id.URL())) + + return CreateX509Certificate(tb, parent, parentKey, options...) +} + +func CreateCertificate(tb testing.TB, tmpl, parent *x509.Certificate, pub, priv interface{}) *x509.Certificate { + certDER, err := x509.CreateCertificate(rand.Reader, tmpl, parent, pub, priv) + require.NoError(tb, err) + cert, err := x509.ParseCertificate(certDER) + require.NoError(tb, err) + return cert +} + +func NewSerial(tb testing.TB) *big.Int { + b := make([]byte, 8) + _, err := rand.Read(b) + require.NoError(tb, err) + return new(big.Int).SetBytes(b) +} + +func WithSerial(serial *big.Int) CertificateOption { + return certificateOption(func(c *x509.Certificate) { + c.SerialNumber = serial + }) +} + +func WithKeyUsage(keyUsage x509.KeyUsage) CertificateOption { + return certificateOption(func(c *x509.Certificate) { + c.KeyUsage = keyUsage + }) +} + +func WithURIs(uris ...*url.URL) CertificateOption { + return certificateOption(func(c *x509.Certificate) { + c.URIs = uris + }) +} + +func WithSubject(subject pkix.Name) CertificateOption { + return certificateOption(func(c *x509.Certificate) { + c.Subject = subject + }) +} + +func applyOptions(c *x509.Certificate, options ...CertificateOption) { + for _, opt := range options { + opt.apply(c) + } +} + +func (ca *CA) chain(includeRoot bool) []*x509.Certificate { + chain := []*x509.Certificate{} + next := ca + for next != nil { + if includeRoot || next.parent != nil { + chain = append(chain, next.cert) + } + next = next.parent + } + return chain +} diff --git a/internal/test/workload_api.go b/internal/test/workload_api.go new file mode 100644 index 00000000..67c22d1f --- /dev/null +++ b/internal/test/workload_api.go @@ -0,0 +1,194 @@ +package test + +import ( + "context" + "crypto/x509" + "errors" + "fmt" + "net" + "sync" + "testing" + + "github.com/spiffe/go-spiffe/v2/bundle/x509bundle" + "github.com/spiffe/go-spiffe/v2/proto/spiffe/workload" + "github.com/spiffe/go-spiffe/v2/svid/x509svid" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +var noIdentityError = status.Error(codes.PermissionDenied, "no identity issued") + +type WorkloadAPI struct { + tb testing.TB + wg sync.WaitGroup + addr string + server *grpc.Server + mu sync.Mutex + x509Resp *workload.X509SVIDResponse + x509Chans map[chan *workload.X509SVIDResponse]struct{} +} + +func NewWorkloadAPI(tb testing.TB) *WorkloadAPI { + w := &WorkloadAPI{ + x509Chans: make(map[chan *workload.X509SVIDResponse]struct{}), + } + + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(tb, err) + + server := grpc.NewServer() + workload.RegisterSpiffeWorkloadAPIServer(server, &workloadAPIWrapper{w: w}) + + w.wg.Add(1) + go func() { + defer w.wg.Done() + _ = server.Serve(listener) + }() + + w.addr = fmt.Sprintf("%s://%s", listener.Addr().Network(), listener.Addr().String()) + tb.Logf("WorkloadAPI address: %s", w.addr) + w.server = server + return w +} + +func (w *WorkloadAPI) Stop() { + w.server.Stop() + w.wg.Wait() +} + +func (w *WorkloadAPI) Addr() string { + return w.addr +} + +func (w *WorkloadAPI) SetX509SVIDResponse(r *X509SVIDResponse) { + var resp *workload.X509SVIDResponse + if r != nil { + resp = r.ToProto(w.tb) + } + + w.mu.Lock() + defer w.mu.Unlock() + w.x509Resp = resp + + for ch := range w.x509Chans { + select { + case ch <- resp: + default: + <-ch + ch <- resp + } + } +} + +func concatRawCertsFromCerts(certs []*x509.Certificate) []byte { + var rawCerts []byte + for _, cert := range certs { + rawCerts = append(rawCerts, cert.Raw...) + } + return rawCerts +} + +func (r *X509SVIDResponse) ToProto(tb testing.TB) *workload.X509SVIDResponse { + var bundle []byte + if r.Bundle != nil { + bundle = concatRawCertsFromCerts(r.Bundle.X509Authorities()) + } + + pb := &workload.X509SVIDResponse{ + FederatedBundles: make(map[string][]byte), + } + for _, svid := range r.SVIDs { + var keyDER []byte + if svid.PrivateKey != nil { + var err error + keyDER, err = x509.MarshalPKCS8PrivateKey(svid.PrivateKey) + require.NoError(tb, err) + } + pb.Svids = append(pb.Svids, &workload.X509SVID{ + SpiffeId: svid.ID.String(), + X509Svid: concatRawCertsFromCerts(svid.Certificates), + X509SvidKey: keyDER, + Bundle: bundle, + }) + } + for _, v := range r.FederatedBundles { + pb.FederatedBundles[v.TrustDomain().IDString()] = concatRawCertsFromCerts(v.X509Authorities()) + } + + return pb +} + +type workloadAPIWrapper struct { + workload.UnimplementedSpiffeWorkloadAPIServer + w *WorkloadAPI +} + +func (w *workloadAPIWrapper) FetchX509SVID(req *workload.X509SVIDRequest, stream workload.SpiffeWorkloadAPI_FetchX509SVIDServer) error { + return w.w.fetchX509SVID(req, stream) +} + +type X509SVIDResponse struct { + SVIDs []*x509svid.SVID + Bundle *x509bundle.Bundle + FederatedBundles []*x509bundle.Bundle +} + +func (w *WorkloadAPI) fetchX509SVID(_ *workload.X509SVIDRequest, stream workload.SpiffeWorkloadAPI_FetchX509SVIDServer) error { + if err := checkHeader(stream.Context()); err != nil { + return err + } + ch := make(chan *workload.X509SVIDResponse, 1) + w.mu.Lock() + w.x509Chans[ch] = struct{}{} + resp := w.x509Resp + w.mu.Unlock() + + defer func() { + w.mu.Lock() + delete(w.x509Chans, ch) + w.mu.Unlock() + }() + + sendResp := func(resp *workload.X509SVIDResponse) error { + if resp == nil { + return noIdentityError + } + return stream.Send(resp) + } + + if err := sendResp(resp); err != nil { + return err + } + for { + select { + case resp := <-ch: + if err := sendResp(resp); err != nil { + return err + } + case <-stream.Context().Done(): + return stream.Context().Err() + } + } +} + +func checkHeader(ctx context.Context) error { + return checkMetadata(ctx, "workload.spiffe.io", "true") +} + +func checkMetadata(ctx context.Context, key, value string) error { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return errors.New("request does not contain metadata") + } + values := md.Get(key) + if len(value) == 0 { + return fmt.Errorf("request metadata does not contain %q value", key) + } + if values[0] != value { + return fmt.Errorf("request metadata %q value is %q; expected %q", key, values[0], value) + } + return nil +} diff --git a/scripts/verify-docs.sh b/scripts/verify-docs.sh index a5b71b0f..4c0ae392 100755 --- a/scripts/verify-docs.sh +++ b/scripts/verify-docs.sh @@ -6,6 +6,6 @@ set -e tmpdir=$(mktemp -d) go run main.go gendoc --dir "$tmpdir" echo "###########################################" -echo "If diffs are found, run: go run ./cmd gendoc" +echo "If diffs are found, run: go run main.go gendoc" echo "###########################################" diff -Naur "$tmpdir" doc/ diff --git a/test-infra/docker-compose.yaml b/test-infra/docker-compose.yaml new file mode 100644 index 00000000..6af67a80 --- /dev/null +++ b/test-infra/docker-compose.yaml @@ -0,0 +1,42 @@ +version: '2' +services: + + spire-server: + pid: "host" + image: gcr.io/spiffe-io/spire-server:1.4.2 + container_name: test-infra_spire-server_1 + hostname: spire-server + volumes: + - ./spire/server:/opt/spire/conf/server + - /tmp/spire/sockets:/run/spire/sockets + command: ["-config", "/opt/spire/conf/server/server.conf"] + ports: + - "8081:8081" + + spire-agent: + pid: "host" + container_name: test-infra_spire-agent_1 + privileged: true + image: gcr.io/spiffe-io/spire-agent:1.4.2 + depends_on: ["spire-server"] + hostname: spire-agent + volumes: + - ./spire/agent:/opt/spire/conf/agent + - /tmp/spire/sockets:/run/spire/sockets + - /proc:/proc + command: ["-config", "/opt/spire/conf/agent/agent.conf"] + + intoto-runner: + container_name: intoto-runner + image: intoto-run:latest + privileged: true + entrypoint: sleep 1000 + build: + context: ../ + dockerfile: ./Dockerfile + target: debug + volumes: + - /tmp/spire/sockets:/run/spire/sockets + - ../test/tmp:/test/tmp + depends_on: + - spire-agent diff --git a/test-infra/infra-down.sh b/test-infra/infra-down.sh new file mode 100755 index 00000000..a8743149 --- /dev/null +++ b/test-infra/infra-down.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +docker-compose -f ./test-infra/docker-compose.yaml down +docker rmi intoto-run:latest +rm -rf /tmp/spire diff --git a/test-infra/infra-up.sh b/test-infra/infra-up.sh new file mode 100755 index 00000000..44cf372d --- /dev/null +++ b/test-infra/infra-up.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +mkdir -p /tmp/spire/sockets +docker-compose -f ./test-infra/docker-compose.yaml up -d spire-server +sleep 5 # spire-server needs to be fully initialized before spire-agent comes up +docker-compose -f ./test-infra/docker-compose.yaml up -d spire-agent +sleep 5 # ensures spire-agent is fully up before registration +sh ./test-infra/register.sh +sleep 5 # ensures package registration is completed before it is required +sh ./test-infra/show-bundle.sh +sleep 5 # ensures spire-agent and spire-server is fully up and diff --git a/test-infra/mint-cert.sh b/test-infra/mint-cert.sh new file mode 100755 index 00000000..734ac570 --- /dev/null +++ b/test-infra/mint-cert.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +docker exec test-infra_spire-server_1 \ +/opt/spire/bin/spire-server x509 mint \ +-socketPath /run/spire/sockets/spire-registration.sock \ +-spiffeID spiffe://example.com/$1 \ +-write . + +docker exec test-infra_spire-server_1 cat svid.pem > ./test/tmp/$1-svid.pem +docker exec test-infra_spire-server_1 cat key.pem > ./test/tmp/$1-key.pem +docker exec test-infra_spire-server_1 cat bundle.pem > ./test/tmp/$1-bundle.pem diff --git a/test-infra/register.sh b/test-infra/register.sh new file mode 100755 index 00000000..436e837d --- /dev/null +++ b/test-infra/register.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +## DEMO REGISTRATIONS ## +#intoto-write-code +docker exec test-infra_spire-server_1 \ +/opt/spire/bin/spire-server entry create \ +-selector unix:uid:1000 \ +-socketPath /run/spire/sockets/spire-registration.sock \ +-spiffeID spiffe://example.com/write-code \ +-parentID spiffe://example.com/spire/agent/sshpop/21Aic_muK032oJMhLfU1_CMNcGmfAnvESeuH5zyFw_g + +#intoto-pakcage +docker exec test-infra_spire-server_1 \ +/opt/spire/bin/spire-server entry create \ +-selector unix:uid:1001 \ +-socketPath /run/spire/sockets/spire-registration.sock \ +-spiffeID spiffe://example.com/package \ +-parentID spiffe://example.com/spire/agent/sshpop/21Aic_muK032oJMhLfU1_CMNcGmfAnvESeuH5zyFw_g diff --git a/test-infra/show-bundle.sh b/test-infra/show-bundle.sh new file mode 100755 index 00000000..eb0a0586 --- /dev/null +++ b/test-infra/show-bundle.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker exec test-infra_spire-server_1 \ +/opt/spire/bin/spire-server bundle show \ +-socketPath /run/spire/sockets/spire-registration.sock diff --git a/test-infra/spire/agent/agent.conf b/test-infra/spire/agent/agent.conf new file mode 100644 index 00000000..0f466fb6 --- /dev/null +++ b/test-infra/spire/agent/agent.conf @@ -0,0 +1,28 @@ +agent { + data_dir = "/run/spire" + log_level = "DEBUG" + server_address = "spire-server" + server_port = "8081" + socket_path = "/run/spire/sockets/agent.sock" + trust_domain = "example.com" + insecure_bootstrap = "true" +} + +plugins { + NodeAttestor "sshpop" { + plugin_data { + host_cert_path = "/opt/spire/conf/agent/agent_ssh_key-cert.pub" + host_key_path = "/opt/spire/conf/agent/agent_ssh_key" + } + } + + KeyManager "memory" { + plugin_data { + } + } + + WorkloadAttestor "unix" { + plugin_data { + } + } +} diff --git a/test-infra/spire/agent/agent_ssh_key b/test-infra/spire/agent/agent_ssh_key new file mode 100644 index 00000000..d2700ca7 --- /dev/null +++ b/test-infra/spire/agent/agent_ssh_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBAFjwLCif6jGFCAXh+wSzEQhP25FLzB1/uzmYQDZOOUgAAAKCdN05XnTdO +VwAAAAtzc2gtZWQyNTUxOQAAACBAFjwLCif6jGFCAXh+wSzEQhP25FLzB1/uzmYQDZOOUg +AAAECqiQ5qAtvGENjROr1TPJqNHr3ipz2o5m/LZJYrfFWDHkAWPAsKJ/qMYUIBeH7BLMRC +E/bkUvMHX+7OZhANk45SAAAAHHRqdWxpYW5AdGp1bGlhbi1DMDJYNzREREpHSDYB +-----END OPENSSH PRIVATE KEY----- diff --git a/test-infra/spire/agent/agent_ssh_key-cert.pub b/test-infra/spire/agent/agent_ssh_key-cert.pub new file mode 100644 index 00000000..f72f8100 --- /dev/null +++ b/test-infra/spire/agent/agent_ssh_key-cert.pub @@ -0,0 +1 @@ +ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIHKePFvG6YhtFQBeMVEw+5cvlZ65YHP2vYpHJuBI/fVxAAAAIEAWPAsKJ/qMYUIBeH7BLMRCE/bkUvMHX+7OZhANk45SAAAAAAAAAAAAAAACAAAACGZvby1ob3N0AAAADAAAAAhmb28taG9zdAAAAAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEAWPAsKJ/qMYUIBeH7BLMRCE/bkUvMHX+7OZhANk45SAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAJGYmukpFo0c0B5lj7OU1Zn4bFA11DFHKwwYgFSJyx0gAdW74KV8wlfIU+wPj6ot0zojZ2F6eDyfETSDESZy4C diff --git a/test-infra/spire/server/server.conf b/test-infra/spire/server/server.conf new file mode 100644 index 00000000..49e50468 --- /dev/null +++ b/test-infra/spire/server/server.conf @@ -0,0 +1,34 @@ +server { + ca_key_type = "rsa-2048" + bind_address = "0.0.0.0" + bind_port = "8081" + trust_domain = "example.com" + data_dir = "/run/spire/data" + log_level = "DEBUG" + socket_path = "/run/spire/sockets/spire-registration.sock" + default_svid_ttl = "720h" + ca_subject = { + country = ["US"], + organization = ["SPIRE"], + common_name = "" + } +} + +plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "datastore.sqlite3" + } + } + + NodeAttestor "sshpop" { + plugin_data { + cert_authorities_path = "/opt/spire/conf/server/ssh_cert_authority.pub" + } + } + + KeyManager "memory" { + plugin_data = {} + } +} diff --git a/test-infra/spire/server/ssh_cert_authority.pub b/test-infra/spire/server/ssh_cert_authority.pub new file mode 100644 index 00000000..63deedf2 --- /dev/null +++ b/test-infra/spire/server/ssh_cert_authority.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEAWPAsKJ/qMYUIBeH7BLMRCE/bkUvMHX+7OZhANk45S