├── test_utils ├── changed_code_for_testing │ ├── LICENSE │ ├── cmd │ │ ├── options │ │ │ └── root.go │ │ ├── sign.go │ │ ├── verify.go │ │ └── commands.go │ └── pkg │ │ └── integrity │ │ └── sha256.go ├── source_for_testing │ └── code_for_testing │ │ ├── LICENSE │ │ ├── cmd │ │ ├── options │ │ │ └── root.go │ │ ├── sign.go │ │ ├── verify.go │ │ └── commands.go │ │ └── pkg │ │ └── integrity │ │ └── sha256.go ├── identical_source_for_testing │ └── code_for_testing │ │ ├── LICENSE │ │ ├── cmd │ │ ├── options │ │ │ └── root.go │ │ ├── sign.go │ │ ├── verify.go │ │ └── commands.go │ │ └── pkg │ │ └── integrity │ │ └── sha256.go └── tasting_keys │ ├── cosign.pub │ ├── cosign_wrong.pub │ └── cosign.key ├── .golangci.yml ├── CONTRIBUTING.md ├── .codecov.yml ├── test ├── cosign.pub ├── e2e_test.sh ├── e2e_test_keyless.sh ├── cosign.key ├── utils │ └── testing_lambda.go └── e2e_test.go ├── .github ├── workflows │ ├── on-push-to-master.yaml │ ├── pre-tests.yaml │ ├── dependency-review.yml │ ├── presubmits.yml │ ├── e2e-tests.yaml │ ├── release.yml │ ├── scorecards.yml │ └── codeql.yml ├── dependabot.yml ├── CODEOWNERS └── settings.yml ├── .gitignore ├── pkg ├── integrity │ ├── hash.go │ ├── env_opertaions.go │ ├── file_operations.go │ ├── identity_generator.go │ ├── docker.go │ └── identity_generator_test.go ├── verify │ ├── errors.go │ └── verifier.go ├── utils │ ├── consts.go │ ├── progress_bar_reader.go │ └── file.go ├── init │ └── aws.go ├── options │ ├── sign_blob.go │ ├── verify.go │ └── sign_image.go ├── clients │ ├── client.go │ └── gcp_client.go └── sign │ └── signer.go ├── cmd └── function-clarity │ ├── cli │ ├── init.go │ ├── deploy.go │ ├── update_func_config.go │ ├── verify.go │ ├── sign.go │ ├── options │ │ └── config.go │ ├── commands.go │ ├── verify │ │ └── verify.go │ ├── gcp │ │ ├── gcp_sign_code.go │ │ └── gcp.go │ ├── sign │ │ └── sign.go │ ├── aws │ │ ├── aws_sign_code.go │ │ ├── init.go │ │ └── aws.go │ └── common │ │ └── aws_sign_image.go │ └── main.go ├── Makefile ├── .licensei.toml ├── FunctionClarity Diagram.drawio ├── aws_function_pkg └── main.go ├── run_env └── utils │ └── unified-template.template ├── LICENSE ├── go.mod └── README.md /test_utils/changed_code_for_testing/LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_utils/source_for_testing/code_for_testing/LICENSE: -------------------------------------------------------------------------------- 1 | aaa -------------------------------------------------------------------------------- /test_utils/identical_source_for_testing/code_for_testing/LICENSE: -------------------------------------------------------------------------------- 1 | aaa -------------------------------------------------------------------------------- /test_utils/changed_code_for_testing/cmd/options/root.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package options 4 | -------------------------------------------------------------------------------- /test_utils/source_for_testing/code_for_testing/cmd/options/root.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package options 4 | -------------------------------------------------------------------------------- /test_utils/identical_source_for_testing/code_for_testing/cmd/options/root.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package options 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | 4 | linters-settings: 5 | golint: 6 | min-confidence: 0.1 7 | goimports: 8 | local-prefixes: github.com/openclarity/function-clarity -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Pull requests and bug reports are welcome! 2 | 3 | For larger changes please create an Issue in GitHub first to discuss your proposed changes and possible implications. 4 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto # auto compares coverage to the previous base commit 6 | comment: 7 | layout: "reach, diff, flags, files" 8 | -------------------------------------------------------------------------------- /test/cosign.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaRXHg/yfMnIajqv/KSBi0jVN+UWa 3 | UtTd5+Y99B/noMLCy+hmCZ5uZBgwpx2kq1r0UtcZk5TIDmCZ3/hTv5GDqQ== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test_utils/tasting_keys/cosign.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErntdNvO/2FpR8UJfhgEMkOTqWbwl 3 | 3AAyI7rE9IMmSpOqJyM8apMeAXcSwKPOiASf0oTnAadyipH8L3AnmdsXww== 4 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /test_utils/tasting_keys/cosign_wrong.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0rNXMuEH+sjubdYng1o7xB+J04Ld 3 | W9FO8QTOGbgbemcOYyz7t0cPVXLazeN4s+qyDTgMHMJkFWglzFmkA2z0JA== 4 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /.github/workflows/on-push-to-master.yaml: -------------------------------------------------------------------------------- 1 | name: On-push-to-master 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | jobs: 8 | pre-test: 9 | runs-on: ubuntu-latest 10 | name: on-push-to-master-step 11 | steps: 12 | - name: Explanation 13 | run: echo "on-push-to-master step run success" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | /bin 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | .vscode 19 | .idea -------------------------------------------------------------------------------- /test_utils/changed_code_for_testing/cmd/sign.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func Sign() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "sign", 13 | Short: "sign and upload the code content", 14 | Run: func(cmd *cobra.Command, args []string) { fmt.Println("sign") }, 15 | } 16 | 17 | return cmd 18 | } 19 | -------------------------------------------------------------------------------- /test_utils/changed_code_for_testing/cmd/verify.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func Verify() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "verify", 13 | Short: "verify code content integrity", 14 | Run: func(cmd *cobra.Command, args []string) { fmt.Println("verify") }, 15 | } 16 | 17 | return cmd 18 | } 19 | -------------------------------------------------------------------------------- /test_utils/source_for_testing/code_for_testing/cmd/sign.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func Sign() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "sign", 13 | Short: "sign and upload the code content", 14 | Run: func(cmd *cobra.Command, args []string) { fmt.Println("sign") }, 15 | } 16 | 17 | return cmd 18 | } 19 | -------------------------------------------------------------------------------- /test_utils/source_for_testing/code_for_testing/cmd/verify.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func Verify() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "verify", 13 | Short: "verify code content integrity", 14 | Run: func(cmd *cobra.Command, args []string) { fmt.Println("verify") }, 15 | } 16 | 17 | return cmd 18 | } 19 | -------------------------------------------------------------------------------- /test_utils/identical_source_for_testing/code_for_testing/cmd/sign.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func Sign() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "sign", 13 | Short: "sign and upload the code content", 14 | Run: func(cmd *cobra.Command, args []string) { fmt.Println("sign") }, 15 | } 16 | 17 | return cmd 18 | } 19 | -------------------------------------------------------------------------------- /test_utils/identical_source_for_testing/code_for_testing/cmd/verify.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func Verify() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "verify", 13 | Short: "verify code content integrity", 14 | Run: func(cmd *cobra.Command, args []string) { fmt.Println("verify") }, 15 | } 16 | 17 | return cmd 18 | } 19 | -------------------------------------------------------------------------------- /test_utils/changed_code_for_testing/cmd/commands.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func New() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "function-clarity", 12 | Short: "cli for signing and verifying functions", 13 | Long: `fdsf sdffsd dfsfds dfsfds`, 14 | } 15 | 16 | cmd.AddCommand(Sign()) 17 | cmd.AddCommand(Verify()) 18 | return cmd 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/pre-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Pre-test 2 | 3 | on: 4 | pull_request_review: 5 | types: [submitted] 6 | push: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | pre-test: 11 | if: > 12 | github.event.push.branch == 'main' || 13 | github.event.review.state == 'approved' 14 | runs-on: ubuntu-latest 15 | name: pre-test-step 16 | steps: 17 | - name: Explanation 18 | run: echo "pre-test step run success" -------------------------------------------------------------------------------- /test/e2e_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "starting..." 3 | cd ./aws_function_pkg 4 | go build 5 | mv ./aws_function_pkg ../test/aws_function 6 | echo "aws_function binary copied to test folder" 7 | 8 | cd ../test/utils 9 | go build testing_lambda.go 10 | echo "testing lambda built successfully" 11 | 12 | cd ../.. 13 | cp ./run_env/utils/unified-template.template ./test/ 14 | echo "stack template copied to test folder" 15 | 16 | echo "e2e tests started" 17 | cd ./test 18 | go test -timeout 30m -v -run "Verify$" 19 | -------------------------------------------------------------------------------- /test/e2e_test_keyless.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "starting..." 3 | cd ./aws_function_pkg 4 | go build 5 | mv ./aws_function_pkg ../test/aws_function 6 | echo "aws_function binary copied to test folder" 7 | 8 | cd ../test/utils 9 | go build testing_lambda.go 10 | echo "testing lambda built successfully" 11 | 12 | cd ../.. 13 | cp ./run_env/utils/unified-template.template ./test/ 14 | echo "stack template copied to test folder" 15 | 16 | echo "e2e tests started" 17 | cd ./test 18 | go test -timeout 30m -v -run "Keyless$" 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /test_utils/source_for_testing/code_for_testing/cmd/commands.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func New() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "function-clarity", 12 | Short: "cli for signing and verifying functions", 13 | Long: `fdsf sdffsd dfsfds dfsfds`, 14 | // Uncomment the following line if your bare application 15 | // has an action associated with it: 16 | //Run: func(cmd *cobra.Command, args []string) { fmt.Println("aaaa") }, 17 | } 18 | 19 | cmd.AddCommand(Sign()) 20 | cmd.AddCommand(Verify()) 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /test_utils/identical_source_for_testing/code_for_testing/cmd/commands.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package cmd 4 | 5 | import ( 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func New() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "function-clarity", 12 | Short: "cli for signing and verifying functions", 13 | Long: `fdsf sdffsd dfsfds dfsfds`, 14 | // Uncomment the following line if your bare application 15 | // has an action associated with it: 16 | //Run: func(cmd *cobra.Command, args []string) { fmt.Println("aaaa") }, 17 | } 18 | 19 | cmd.AddCommand(Sign()) 20 | cmd.AddCommand(Verify()) 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /test/cosign.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 3 | OCwicCI6MX0sInNhbHQiOiJyVXUwM3JibGt1bko0enhyQU1VVFh0TTYxUE42Uzd5 4 | WE45RnE2RVYwQU1JPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiJmOGphUTlJUXV0YVlCZnl4VDlpc1F6dDlSOTNFd2w4ZCJ9LCJj 6 | aXBoZXJ0ZXh0IjoidFF4dnNZQ1FPcE5neE0zaGhQZDgrUlhGbXhUbURyRHREYU9Q 7 | cVdEcjF5WExKNFdkQ3JHMkVQUW1DMGtQUEFkUC9hOG9zSXZERHRRZlVPbmpIaEk2 8 | RC80UDZEdzZEdjZQc2NOdW5JZzEraDVMTGJLeHduVVhmdUZkZlZVWTBqN3VUNUNU 9 | UFNvZnUwN3JGeWt6YkVUaG9ENVM1dEFWNVczS0l0cm5jMnMwWU1mbEhNM010SzBF 10 | N2lsQnJHeno0NW04Ym9aM2FnMU1VdGZoUHc9PSJ9 11 | -----END ENCRYPTED COSIGN PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /test_utils/tasting_keys/cosign.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 3 | OCwicCI6MX0sInNhbHQiOiJrZVJsbXc3QVF5YThyd1VLYVZJS2dUZ2htV3JCaUZw 4 | M2VsQVFkdE5ld1JVPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiJJUVBpRXN0QjZUV3JBQnNPTk9DdHRpMXR6OG1NcWRZMCJ9LCJj 6 | aXBoZXJ0ZXh0IjoieDBjcHltcjlsdDdFUmtHa3VUVllvUU5tcG5BUTNTK1VqSSth 7 | cFV0Y3Uxa21UK0xEbWJpTXVDci92Y1hGbDJwVC9JWXN6bDF4bk9yYW9BNWd4Z2Vk 8 | cnZYeGJPMHJITDdhYWpMVlZ4R3RtUDBzVmhZdzZvYkNyQm5hcjFDYWxVMVpKUHJz 9 | MnhLZnpCOE02bExwaWlYS1dKeUhQMVg2VmRUSVhlSGI0WkZjTFJWbFlFbzVyZWVx 10 | N1FrLzNVVEZqbjUzVWZoNFYvczVYckNlM1E9PSJ9 11 | -----END ENCRYPTED COSIGN PRIVATE KEY----- -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS reference: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | 3 | # These owners will be the default owners for everything in 4 | # the repo. Unless a later match takes precedence, 5 | # the following users/teams will be requested for 6 | # review when someone opens a pull request. 7 | # 8 | # Maintainers 9 | # - Naor Shmuel (@naor-cisco) 10 | # - Shai Embon (@embonshai) 11 | # - Roman Medvedovski (@rmedvedo) 12 | * @openclarity/functionclarity-maintainers 13 | 14 | # Enforces admin protections for repo configuration via probot settings app. 15 | # ref: https://github.com/probot/settings#security-implications 16 | .github/settings.yml @openclarity/functionclarity-admins @openclarity/org-admins 17 | -------------------------------------------------------------------------------- /pkg/integrity/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package integrity 17 | 18 | type Hash interface { 19 | GenerateIdentity(path string) (string, error) 20 | } 21 | -------------------------------------------------------------------------------- /test/utils/testing_lambda.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "github.com/aws/aws-lambda-go/lambda" 21 | ) 22 | 23 | func HandleRequest() { 24 | fmt.Println("e2eTest run") 25 | } 26 | 27 | func main() { 28 | lambda.Start(HandleRequest) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/verify/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package verify 17 | 18 | import ( 19 | "fmt" 20 | ) 21 | 22 | type VerifyError struct { 23 | Err error 24 | } 25 | 26 | func (e VerifyError) Error() string { 27 | return fmt.Sprintf("verification error: %v", e.Err) 28 | } 29 | func (m VerifyError) Is(target error) bool { 30 | return target == VerifyError{} 31 | } 32 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/init.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cli 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/aws" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func Init() *cobra.Command { 24 | cmd := &cobra.Command{ 25 | Use: "init", 26 | Short: "init cloud provider configuration", 27 | } 28 | cmd.AddCommand(aws.AwsInit()) 29 | return cmd 30 | } 31 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/deploy.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cli 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/aws" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func Deploy() *cobra.Command { 24 | cmd := &cobra.Command{ 25 | Use: "deploy", 26 | Short: "Deploy function clarity to cloud provider", 27 | } 28 | cmd.AddCommand(aws.AwsDeploy()) 29 | return cmd 30 | } 31 | -------------------------------------------------------------------------------- /cmd/function-clarity/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli" 20 | "github.com/openclarity/functionclarity/pkg/utils" 21 | "log" 22 | "os" 23 | ) 24 | 25 | func main() { 26 | if err := os.MkdirAll(utils.FunctionClarityHomeDir, os.ModePerm); err != nil { 27 | log.Fatal("Can't create home dir", err) 28 | } 29 | cli.New().Execute() //nolint:errcheck 30 | } 31 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/update_func_config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cli 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/aws" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func UpdateFuncConfig() *cobra.Command { 24 | cmd := &cobra.Command{ 25 | Use: "update-func-config", 26 | Short: "Update verifier function runtime settings", 27 | } 28 | cmd.AddCommand(aws.AwsUpdateFuncConfig()) 29 | return cmd 30 | } 31 | -------------------------------------------------------------------------------- /pkg/integrity/env_opertaions.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package integrity 17 | 18 | import ( 19 | "github.com/spf13/viper" 20 | "log" 21 | "os" 22 | "strconv" 23 | ) 24 | 25 | const ExperimentalEnv = "COSIGN_EXPERIMENTAL" 26 | 27 | func IsExperimentalEnv() bool { 28 | env, err := strconv.ParseBool(os.Getenv(ExperimentalEnv)) 29 | if err != nil { 30 | log.Fatal("can't read env variable") 31 | } 32 | config := viper.GetBool("isKeyless") 33 | if env || config { 34 | return true 35 | } 36 | return false 37 | } 38 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cli 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/aws" 20 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/gcp" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func Verify() *cobra.Command { 25 | cmd := &cobra.Command{ 26 | Use: "verify", 27 | Short: "verify function's code/image integrity", 28 | } 29 | cmd.AddCommand(aws.AwsVerify()) 30 | cmd.AddCommand(gcp.GcpVerify()) 31 | return cmd 32 | } 33 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/sign.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cli 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/aws" 20 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/gcp" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func Sign() *cobra.Command { 25 | cmd := &cobra.Command{ 26 | Use: "sign", 27 | Short: "sign image/folder and upload the signature to cloud provider", 28 | } 29 | cmd.AddCommand(aws.AwsSign()) 30 | cmd.AddCommand(gcp.GcpSign()) 31 | return cmd 32 | } 33 | -------------------------------------------------------------------------------- /pkg/utils/consts.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package utils 17 | 18 | import "os" 19 | 20 | const FunctionSignedTagValue = "Function signed and verified" 21 | 22 | const FunctionNotSignedTagValue = "Function not signed" 23 | 24 | const FunctionVerifyResultTagKey = "Function clarity result" 25 | 26 | const FunctionClarityConcurrencyTagKey = "FUNCTION_CLARITY_CONCURRENCY_LEVEL" 27 | 28 | const FunctionClaritySignatureNotFoundMessage = "storage: object doesn't exist" 29 | 30 | var HomeDir, _ = os.UserHomeDir() 31 | 32 | var FunctionClarityHomeDir = HomeDir + "/function-clarity/" 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | GOLANGCI_VERSION = 1.49.0 3 | LICENSEI_VERSION = 0.5.0 4 | 5 | 6 | 7 | bin/licensei: bin/licensei-${LICENSEI_VERSION} 8 | @ln -sf licensei-${LICENSEI_VERSION} bin/licensei 9 | bin/licensei-${LICENSEI_VERSION}: 10 | @mkdir -p bin 11 | curl -sfL https://raw.githubusercontent.com/goph/licensei/master/install.sh | bash -s v${LICENSEI_VERSION} 12 | @mv bin/licensei $@ 13 | 14 | .PHONY: license-check 15 | license-check: bin/licensei ## Run license check 16 | ./bin/licensei check 17 | ./bin/licensei header 18 | 19 | 20 | bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION} 21 | @ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint 22 | 23 | bin/golangci-lint-${GOLANGCI_VERSION}: 24 | @mkdir -p bin 25 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION} 26 | @mv bin/golangci-lint $@ 27 | 28 | 29 | .PHONY: lint 30 | lint: bin/golangci-lint ## Run linter 31 | ./bin/golangci-lint run -v 32 | 33 | .PHONY: test 34 | test: ## Run Unit Tests 35 | @(go test -v -covermode=atomic -coverprofile=unit-coverage.out ./cmd/... ./pkg/...) 36 | 37 | .PHONY: check 38 | check: lint test 39 | 40 | .PHONY: fix 41 | fix: bin/golangci-lint ## Fix lint violations 42 | ./bin/golangci-lint run --fix 43 | -------------------------------------------------------------------------------- /pkg/init/aws.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package init 17 | 18 | type AWSInput struct { 19 | AccessKey string 20 | SecretKey string 21 | Region string 22 | Bucket string 23 | Action string 24 | PublicKey string 25 | PrivateKey string 26 | CloudTrail CloudTrail 27 | IsKeyless bool 28 | SnsTopicArn string 29 | IncludedFuncTagKeys []string 30 | IncludedFuncRegions []string 31 | BucketPathToPublicKeys string 32 | } 33 | 34 | type CloudTrail struct { 35 | Name string 36 | } 37 | -------------------------------------------------------------------------------- /pkg/integrity/file_operations.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package integrity 17 | 18 | import ( 19 | "os" 20 | "path/filepath" 21 | ) 22 | 23 | func ReadFile(path string) ([]byte, error) { 24 | var raw []byte 25 | var err error 26 | raw, err = os.ReadFile(filepath.Clean(path)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return raw, nil 31 | } 32 | 33 | func SaveTextToFile(text string, path string) error { 34 | f, err := os.Create(path) 35 | if err != nil { 36 | return err 37 | } 38 | defer f.Close() 39 | if _, err := f.WriteString(text); err != nil { 40 | return err 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Harden Runner 18 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 19 | with: 20 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 21 | 22 | - name: 'Checkout Repository' 23 | uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 24 | - name: 'Dependency Review' 25 | uses: actions/dependency-review-action@0ff3da6f81b812d4ec3cf37a04e2308c7a723730 26 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/options/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package options 17 | 18 | import ( 19 | "fmt" 20 | "github.com/openclarity/functionclarity/pkg/utils" 21 | "github.com/spf13/viper" 22 | ) 23 | 24 | var Config string = "" 25 | 26 | func CobraInit() { 27 | if Config != "" { 28 | viper.SetConfigFile(Config) 29 | viper.SetConfigType("yaml") 30 | } else { 31 | home := utils.HomeDir 32 | viper.AddConfigPath(home) 33 | viper.SetConfigName(".fc") 34 | viper.SetConfigType("yaml") 35 | } 36 | if err := viper.ReadInConfig(); err != nil { 37 | fmt.Printf("Error loading config file: %s\n", err) 38 | } 39 | if viper.ConfigFileUsed() != "" { 40 | fmt.Printf("using config file: %s\n", viper.ConfigFileUsed()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test_utils/changed_code_for_testing/pkg/integrity/sha256.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package integrity 4 | 5 | import ( 6 | "crypto/sha256" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "sort" 13 | "strings" 14 | ) 15 | 16 | type IdentityGen interface { 17 | GenerateIdentity(path string) (string, error) 18 | } 19 | 20 | type Sha256 struct{} 21 | 22 | func (o *Sha256) GenerateIdentity(path string) (string, error) { 23 | var hashArray []string 24 | rootFolderName := "" 25 | err := filepath.Walk(path, 26 | func(path string, info os.FileInfo, err error) error { 27 | if !info.IsDir() { 28 | data, err := ioutil.ReadFile(path) 29 | if err != nil { 30 | fmt.Println("File reading error", err) 31 | } 32 | dataString := fmt.Sprintf("%x", data) 33 | dataString = dataString + path[strings.Index(path, rootFolderName)+len(rootFolderName):] 34 | sha := sha256.Sum256([]byte(dataString)) 35 | fmt.Printf("%s %x\n", path[strings.Index(path, rootFolderName)+len(rootFolderName):], sha) 36 | hashArray = append(hashArray, fmt.Sprintf("%x", sha)) 37 | } else if rootFolderName == "" { 38 | rootFolderName = info.Name() 39 | } 40 | return nil 41 | }) 42 | if err != nil { 43 | log.Println(err) 44 | } 45 | sort.Strings(hashArray) 46 | joinedShaString := strings.Join(hashArray[:], ",") 47 | sha256 := sha256.Sum256([]byte(joinedShaString)) 48 | return fmt.Sprintf("%x\n", sha256), nil 49 | } 50 | -------------------------------------------------------------------------------- /test_utils/source_for_testing/code_for_testing/pkg/integrity/sha256.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package integrity 4 | 5 | import ( 6 | "crypto/sha256" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "sort" 13 | "strings" 14 | ) 15 | 16 | type IdentityGen interface { 17 | GenerateIdentity(path string) (string, error) 18 | } 19 | 20 | type Sha256 struct{} 21 | 22 | func (o *Sha256) GenerateIdentity(path string) (string, error) { 23 | var hashArray []string 24 | rootFolderName := "" 25 | err := filepath.Walk(path, 26 | func(path string, info os.FileInfo, err error) error { 27 | if !info.IsDir() { 28 | data, err := ioutil.ReadFile(path) 29 | if err != nil { 30 | fmt.Println("File reading error", err) 31 | } 32 | dataString := fmt.Sprintf("%x", data) 33 | dataString = dataString + path[strings.Index(path, rootFolderName)+len(rootFolderName):] 34 | sha := sha256.Sum256([]byte(dataString)) 35 | fmt.Printf("%s %x\n", path[strings.Index(path, rootFolderName)+len(rootFolderName):], sha) 36 | hashArray = append(hashArray, fmt.Sprintf("%x", sha)) 37 | } else if rootFolderName == "" { 38 | rootFolderName = info.Name() 39 | } 40 | return nil 41 | }) 42 | if err != nil { 43 | log.Println(err) 44 | } 45 | sort.Strings(hashArray) 46 | joinedShaString := strings.Join(hashArray[:], ",") 47 | sha256 := sha256.Sum256([]byte(joinedShaString)) 48 | return fmt.Sprintf("%x\n", sha256), nil 49 | } 50 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/commands.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cli 17 | 18 | import ( 19 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/options" 20 | "github.com/sigstore/cosign/cmd/cosign/cli" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func New() *cobra.Command { 25 | cmd := &cobra.Command{ 26 | Use: "function-clarity", 27 | Short: "cli for signing and verifying function content", 28 | Long: `cli for signing and verifying function content`, 29 | } 30 | 31 | cmd.AddCommand(Sign()) 32 | cmd.AddCommand(Verify()) 33 | cmd.AddCommand(cli.GenerateKeyPair()) 34 | cmd.AddCommand(cli.ImportKeyPair()) 35 | cmd.AddCommand(Init()) 36 | cmd.AddCommand(Deploy()) 37 | cmd.AddCommand(UpdateFuncConfig()) 38 | cobra.OnInitialize(options.CobraInit) 39 | return cmd 40 | } 41 | -------------------------------------------------------------------------------- /test_utils/identical_source_for_testing/code_for_testing/pkg/integrity/sha256.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package integrity 4 | 5 | import ( 6 | "crypto/sha256" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "sort" 13 | "strings" 14 | ) 15 | 16 | type IdentityGen interface { 17 | GenerateIdentity(path string) (string, error) 18 | } 19 | 20 | type Sha256 struct{} 21 | 22 | func (o *Sha256) GenerateIdentity(path string) (string, error) { 23 | var hashArray []string 24 | rootFolderName := "" 25 | err := filepath.Walk(path, 26 | func(path string, info os.FileInfo, err error) error { 27 | if !info.IsDir() { 28 | data, err := ioutil.ReadFile(path) 29 | if err != nil { 30 | fmt.Println("File reading error", err) 31 | } 32 | dataString := fmt.Sprintf("%x", data) 33 | dataString = dataString + path[strings.Index(path, rootFolderName)+len(rootFolderName):] 34 | sha := sha256.Sum256([]byte(dataString)) 35 | fmt.Printf("%s %x\n", path[strings.Index(path, rootFolderName)+len(rootFolderName):], sha) 36 | hashArray = append(hashArray, fmt.Sprintf("%x", sha)) 37 | } else if rootFolderName == "" { 38 | rootFolderName = info.Name() 39 | } 40 | return nil 41 | }) 42 | if err != nil { 43 | log.Println(err) 44 | } 45 | sort.Strings(hashArray) 46 | joinedShaString := strings.Join(hashArray[:], ",") 47 | sha256 := sha256.Sum256([]byte(joinedShaString)) 48 | return fmt.Sprintf("%x\n", sha256), nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/utils/progress_bar_reader.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package utils 17 | 18 | import ( 19 | "github.com/vbauerster/mpb/v5" 20 | "os" 21 | "sync" 22 | ) 23 | 24 | type ProgressBarReader struct { 25 | Fp *os.File 26 | Size int64 27 | read int64 28 | Bar *mpb.Bar 29 | SignMap map[int64]struct{} 30 | mux sync.Mutex 31 | } 32 | 33 | func (r *ProgressBarReader) Read(p []byte) (int, error) { 34 | return r.Fp.Read(p) 35 | } 36 | 37 | func (r *ProgressBarReader) ReadAt(p []byte, off int64) (int, error) { 38 | n, err := r.Fp.ReadAt(p, off) 39 | if err != nil { 40 | return n, err 41 | } 42 | 43 | r.Bar.SetTotal(r.Size, false) 44 | 45 | r.mux.Lock() 46 | // Ignore the first signature call 47 | if _, ok := r.SignMap[off]; ok { 48 | r.read += int64(n) 49 | r.Bar.SetCurrent(r.read) 50 | } else { 51 | r.SignMap[off] = struct{}{} 52 | } 53 | r.mux.Unlock() 54 | 55 | return n, err 56 | } 57 | 58 | func (r *ProgressBarReader) Seek(offset int64, whence int) (int64, error) { 59 | return r.Fp.Seek(offset, whence) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/options/sign_blob.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package options 17 | 18 | import ( 19 | "github.com/sigstore/cosign/cmd/cosign/cli/options" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | type SignBlobOptions struct { 24 | options.SignBlobOptions 25 | } 26 | 27 | func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { 28 | o.SecurityKey.AddFlags(cmd) 29 | o.Fulcio.AddFlags(cmd) 30 | o.Rekor.AddFlags(cmd) 31 | o.OIDC.AddFlags(cmd) 32 | o.Registry.AddFlags(cmd) 33 | 34 | cmd.Flags().BoolVar(&o.Base64Output, "b64", true, 35 | "whether to base64 encode the output") 36 | 37 | cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", 38 | "write the signature to FILE") 39 | 40 | cmd.Flags().StringVar(&o.Output, "output", "", "write the signature to FILE") 41 | 42 | cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", 43 | "write the certificate to FILE") 44 | 45 | cmd.Flags().StringVar(&o.BundlePath, "bundle", "", 46 | "write everything required to verify the blob to a FILE") 47 | 48 | cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, 49 | "skip confirmation prompts for non-destructive operations") 50 | } 51 | -------------------------------------------------------------------------------- /pkg/clients/client.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package clients 17 | 18 | type Notification struct { 19 | AccountId string 20 | FunctionName string 21 | FunctionIdentifier string 22 | Action string 23 | Region string 24 | } 25 | 26 | const ConfigEnvVariableName = "CONFIGURATION" 27 | 28 | type Client interface { 29 | ResolvePackageType(funcIdentifier string) (string, error) 30 | GetFuncCode(funcIdentifier string) (string, error) 31 | GetFuncImageURI(funcIdentifier string) (string, error) 32 | GetFuncHash(funcIdentifier string) (string, error) 33 | IsFuncInRegions(regions []string) bool 34 | FuncContainsTags(funcIdentifier string, tagKes []string) (bool, error) 35 | Upload(signature string, identity string, isKeyless bool) error 36 | DownloadSignature(fileName string, outputType string, bucketPathToSignatures string) error 37 | HandleBlock(funcIdentifier *string, failed bool) error 38 | HandleDetect(funcIdentifier *string, failed bool) error 39 | Notify(msg string, snsArn string) error 40 | FillNotificationDetails(notification *Notification, functionIdentifier string) error 41 | DownloadPublicKeys(path string) (string, error) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/options/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package options 17 | 18 | import ( 19 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | type VerifyOpts struct { 24 | BundlePath string 25 | co.VerifyOptions 26 | } 27 | 28 | func (o *VerifyOpts) AddFlags(cmd *cobra.Command) { 29 | o.SecurityKey.AddFlags(cmd) 30 | o.Rekor.AddFlags(cmd) 31 | o.CertVerify.AddFlags(cmd) 32 | o.Registry.AddFlags(cmd) 33 | o.SignatureDigest.AddFlags(cmd) 34 | o.AnnotationOptions.AddFlags(cmd) 35 | 36 | cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true, 37 | "whether to check the claims found") 38 | 39 | cmd.Flags().StringVar(&o.Attachment, "attachment", "", 40 | "related image attachment to sign (sbom), default none") 41 | 42 | cmd.Flags().StringVarP(&o.Output, "output", "o", "json", 43 | "output format for the signing image information (json|text)") 44 | 45 | cmd.Flags().StringVar(&o.SignatureRef, "signature", "", 46 | "signature content or path or remote URL") 47 | 48 | cmd.Flags().BoolVar(&o.LocalImage, "local-image", false, 49 | "whether the specified image is a path to an image saved locally via 'cosign save'") 50 | 51 | cmd.Flags().StringVar(&o.BundlePath, "bundle", "", 52 | "path to bundle FILE") 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/presubmits.yml: -------------------------------------------------------------------------------- 1 | name: presubmits 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: # added using https://github.com/step-security/secure-workflows 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Harden Runner 17 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 18 | with: 19 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 20 | 21 | - name: Checkout 22 | uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 23 | - name: Set up Go 24 | uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 25 | with: 26 | go-version: 1.19 27 | check-latest: true 28 | cache: true 29 | 30 | - name: Build 31 | run: go build -v ./... 32 | 33 | verify: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Harden Runner 37 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 38 | with: 39 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 40 | 41 | - name: Checkout 42 | uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 43 | - name: Set up Go 44 | uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 45 | with: 46 | go-version: 1.19 47 | check-latest: true 48 | cache: true 49 | 50 | - name: Check licenses 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | run: make license-check 54 | 55 | - name: Run linter and unit tests 56 | run: make check 57 | 58 | - name: Upload coverage to Codecov 59 | uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 60 | with: 61 | files: ./unit-coverage.out 62 | verbose: true -------------------------------------------------------------------------------- /pkg/integrity/identity_generator.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package integrity 17 | 18 | import ( 19 | "crypto/sha256" 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | "sort" 24 | "strings" 25 | ) 26 | 27 | type IdentityGenerator interface { 28 | GenerateIdentity(path string) (string, error) 29 | } 30 | 31 | type Sha256 struct{} 32 | 33 | func (o *Sha256) GenerateIdentity(path string) (string, error) { 34 | var identities []string 35 | rootFolderName := "" 36 | err := filepath.WalkDir(path, 37 | func(path string, d os.DirEntry, err error) error { 38 | if err != nil { 39 | return err 40 | } 41 | if !d.IsDir() { 42 | data, err := os.ReadFile(path) 43 | if err != nil { 44 | return err 45 | } 46 | dataString := fmt.Sprintf("%x", data) 47 | if rootFolderName == "" { 48 | dataString = dataString + d.Name() 49 | } else { 50 | dataString = dataString + path[strings.Index(path, rootFolderName)+len(rootFolderName)+1:] 51 | } 52 | sha := sha256.Sum256([]byte(dataString)) 53 | identities = append(identities, fmt.Sprintf("%x", sha)) 54 | } else if rootFolderName == "" { 55 | rootFolderName = d.Name() 56 | } 57 | return nil 58 | }) 59 | if err != nil { 60 | return "", err 61 | } 62 | sort.Strings(identities) 63 | joinedShaString := strings.Join(identities[:], ",") 64 | identitiesSha256 := sha256.Sum256([]byte(joinedShaString)) 65 | return fmt.Sprintf("%x", identitiesSha256), nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/sign/signer.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package sign 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/sign" 22 | "github.com/openclarity/functionclarity/pkg/clients" 23 | "github.com/openclarity/functionclarity/pkg/integrity" 24 | "github.com/openclarity/functionclarity/pkg/options" 25 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 26 | "github.com/spf13/viper" 27 | ) 28 | 29 | func SignAndUploadCode(client clients.Client, codePath string, o *options.SignBlobOptions, ro *co.RootOptions) error { 30 | hash := new(integrity.Sha256) 31 | codeIdentity, err := hash.GenerateIdentity(codePath) 32 | if err != nil { 33 | return fmt.Errorf("failed to create identity: %w", err) 34 | } 35 | isKeyless := false 36 | privateKey := viper.GetString("privatekey") 37 | if !o.SecurityKey.Use && privateKey == "" && integrity.IsExperimentalEnv() { 38 | isKeyless = true 39 | } 40 | 41 | signedIdentity, err := sign.SignIdentity(codeIdentity, o, ro, isKeyless) 42 | if err != nil { 43 | return fmt.Errorf("failed to sign identity: %s with private key in path: %s: %w", codeIdentity, privateKey, err) 44 | } 45 | if err = client.Upload(signedIdentity, codeIdentity, isKeyless); err != nil { 46 | return fmt.Errorf("failed to upload code signature: identity: %s, signature: %s to bucket: %s: %w", codeIdentity, signedIdentity, viper.GetString("bucket"), err) 47 | } 48 | fmt.Println("Code uploaded successfully") 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/integrity/docker.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package integrity 17 | 18 | import ( 19 | "encoding/base64" 20 | "encoding/json" 21 | "github.com/openclarity/functionclarity/pkg/utils" 22 | "os" 23 | "strings" 24 | 25 | "github.com/openclarity/functionclarity/pkg/clients" 26 | ) 27 | 28 | type Auth struct { 29 | Username string `json:"username"` 30 | Password string `json:"password"` 31 | } 32 | 33 | type DockerAuth struct { 34 | Auths map[string]Auth `json:"auths"` 35 | } 36 | 37 | func InitDocker(awsClient *clients.AwsClient) error { 38 | ecrToken, err := awsClient.GetEcrToken() 39 | if err != nil { 40 | return err 41 | } 42 | dockerAuth := DockerAuth{Auths: map[string]Auth{}} 43 | for _, ad := range ecrToken.AuthorizationData { 44 | usernamePassword, err := base64.StdEncoding.DecodeString(*ad.AuthorizationToken) 45 | if err != nil { 46 | return err 47 | } 48 | split := strings.Split(string(usernamePassword), ":") 49 | dockerAuth.Auths[*ad.ProxyEndpoint] = Auth{ 50 | Username: split[0], 51 | Password: split[1], 52 | } 53 | } 54 | if err != nil { 55 | return err 56 | } 57 | 58 | homeDir := utils.HomeDir 59 | 60 | dockerConfigDir := homeDir + "/.docker" 61 | err = os.MkdirAll(dockerConfigDir, 0700) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | dockerConfigJson, err := json.Marshal(dockerAuth) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | err = os.WriteFile(dockerConfigDir+"/config.json", dockerConfigJson, 0600) 72 | if err != nil { 73 | return err 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /.licensei.toml: -------------------------------------------------------------------------------- 1 | approved = [ 2 | "mit", 3 | "apache-2.0", 4 | "bsd-3-clause", 5 | "bsd-2-clause", 6 | "mpl-2.0", 7 | "isc" 8 | ] 9 | ignored = [ 10 | "go.mongodb.org/mongo-driver", # Apache 2.0 - https://github.com/mongodb/mongo-go-driver/blob/master/LICENSE 11 | "gopkg.in/square/go-jose.v2", # Apache 2.0 - https://github.com/square/go-jose/blob/v2.6.0/LICENSE 12 | "github.com/gogo/protobuf", # BSD-3-Clause - https://pkg.go.dev/github.com/gogo/protobuf?tab=licenses 13 | "google.golang.org/protobuf", # BSD-3-Clause - https://pkg.go.dev/google.golang.org/protobuf?tab=licenses 14 | "sigs.k8s.io/yaml", # MIT - https://github.com/kubernetes-sigs/yaml/blob/master/LICENSE 15 | "github.com/vbauerster/mpb/v5", # The Unlicense - https://github.com/vbauerster/mpb/blob/master/UNLICENSE 16 | "cuelang.org/go", # Apache 2.0 - https://github.com/cue-lang/cue/blob/master/LICENSE 17 | "github.com/xeipuuv/gojsonpointer", # Apache 2.0 - https://github.com/xeipuuv/gojsonpointer/blob/master/LICENSE-APACHE-2.0.txt 18 | "github.com/xeipuuv/gojsonreference", # Apache 2.0 - https://github.com/xeipuuv/gojsonreference/blob/master/LICENSE-APACHE-2.0.txt 19 | "github.com/rcrowley/go-metrics", # BSD-3-Clause - https://github.com/rcrowley/go-metrics/blob/master/LICENSE 20 | "github.com/ghodss/yaml", # MIT -https://github.com/ghodss/yaml/blob/master/LICENSE 21 | "github.com/alibabacloud-go/tea-xml", # Apache 2.0 - https://github.com/alibabacloud-go/tea-xml#license 22 | ] 23 | 24 | [header] 25 | ignorePaths = ["test_utils"] 26 | ignoreFiles = [] 27 | template = """// Copyright © :YEAR: Cisco Systems, Inc. and its affiliates. 28 | // All rights reserved. 29 | // 30 | // Licensed under the Apache License, Version 2.0 (the "License"); 31 | // you may not use this file except in compliance with the License. 32 | // You may obtain a copy of the License at 33 | // 34 | // http://www.apache.org/licenses/LICENSE-2.0 35 | // 36 | // Unless required by applicable law or agreed to in writing, software 37 | // distributed under the License is distributed on an "AS IS" BASIS, 38 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 39 | // See the License for the specific language governing permissions and 40 | // limitations under the License.""" -------------------------------------------------------------------------------- /cmd/function-clarity/cli/verify/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package verify 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "github.com/google/uuid" 22 | "github.com/openclarity/functionclarity/pkg/integrity" 23 | opts "github.com/openclarity/functionclarity/pkg/options" 24 | "github.com/openclarity/functionclarity/pkg/utils" 25 | "github.com/sigstore/cosign/cmd/cosign/cli/options" 26 | "github.com/sigstore/cosign/cmd/cosign/cli/verify" 27 | ) 28 | 29 | func VerifyIdentity(identity string, o *opts.VerifyOpts, ctx context.Context, isKeyless bool) error { 30 | path := utils.FunctionClarityHomeDir + uuid.New().String() 31 | if err := integrity.SaveTextToFile(identity, path); err != nil { 32 | return err 33 | } 34 | 35 | ko := options.KeyOpts{ 36 | KeyRef: o.Key, 37 | Sk: o.SecurityKey.Use, 38 | Slot: o.SecurityKey.Slot, 39 | RekorURL: o.Rekor.URL, 40 | BundlePath: o.BundlePath, 41 | } 42 | 43 | certRef := o.CertVerify.Cert 44 | if isKeyless { 45 | certRef = utils.FunctionClarityHomeDir + identity + ".crt.base64" 46 | } 47 | sigRef := utils.FunctionClarityHomeDir + identity + ".sig" 48 | 49 | if err := verify.VerifyBlobCmd(ctx, ko, certRef, 50 | o.CertVerify.CertEmail, o.CertVerify.CertIdentity, o.CertVerify.CertOidcIssuer, o.CertVerify.CertChain, 51 | sigRef, path, o.CertVerify.CertGithubWorkflowTrigger, o.CertVerify.CertGithubWorkflowSha, 52 | o.CertVerify.CertGithubWorkflowName, o.CertVerify.CertGithubWorkflowRepository, o.CertVerify.CertGithubWorkflowRef, 53 | o.CertVerify.EnforceSCT); err != nil { 54 | return fmt.Errorf("verifying identity %s: %w", identity, err) 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/integrity/identity_generator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package integrity 17 | 18 | import ( 19 | "testing" 20 | ) 21 | 22 | func TestGenerateIdentityIdempotence(t *testing.T) { 23 | const pathToSourceCode = "../../test_utils/source_for_testing/code_for_testing/" 24 | const pathToIdenticalSourceCode = "../../test_utils/identical_source_for_testing/code_for_testing/" 25 | 26 | integrityCalculator := Sha256{} 27 | generateIdentity, err := integrityCalculator.GenerateIdentity(pathToSourceCode) 28 | if err != nil { 29 | t.Fatalf("Failed to generate code identity for code in: %s", pathToSourceCode) 30 | } 31 | 32 | identicalGenerateIdentity, err := integrityCalculator.GenerateIdentity(pathToIdenticalSourceCode) 33 | if err != nil { 34 | t.Fatalf("Failed to generate code identity for code in: %s", pathToIdenticalSourceCode) 35 | } 36 | 37 | if generateIdentity != identicalGenerateIdentity { 38 | t.Fatalf("Error. The generated identities aren't consistent") 39 | } 40 | } 41 | 42 | func TestGenerateIdentityUniqueness(t *testing.T) { 43 | const pathToSourceCode = "../../test_utils/source_for_testing/code_for_testing/" 44 | const pathToChangedSourceCode = "../../test_utils/changed_code_for_testing/" 45 | 46 | integrityCalculator := Sha256{} 47 | generateIdentity, err := integrityCalculator.GenerateIdentity(pathToSourceCode) 48 | if err != nil { 49 | t.Fatalf("Failed to generate code identity for code in: %s", pathToSourceCode) 50 | } 51 | 52 | identicalGenerateIdentity, err := integrityCalculator.GenerateIdentity(pathToChangedSourceCode) 53 | if err != nil { 54 | t.Fatalf("Failed to generate code identity for code in: %s", pathToChangedSourceCode) 55 | } 56 | 57 | if generateIdentity == identicalGenerateIdentity { 58 | t.Fatalf("Error. The generated identities should be diffrent") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/gcp/gcp_sign_code.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package gcp 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/options" 22 | "github.com/openclarity/functionclarity/pkg/clients" 23 | o "github.com/openclarity/functionclarity/pkg/options" 24 | "github.com/openclarity/functionclarity/pkg/sign" 25 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 26 | "github.com/spf13/cobra" 27 | "github.com/spf13/viper" 28 | ) 29 | 30 | func GCPSignCode() *cobra.Command { 31 | sbo := &o.SignBlobOptions{} 32 | ro := &co.RootOptions{} 33 | 34 | cmd := &cobra.Command{ 35 | Use: "code", 36 | Short: "sign code content and upload its signature to GCP", 37 | Args: cobra.ExactArgs(1), 38 | PreRunE: func(cmd *cobra.Command, args []string) error { 39 | if err := viper.BindPFlag("location", cmd.Flags().Lookup("location")); err != nil { 40 | return fmt.Errorf("error binding location: %w", err) 41 | } 42 | if err := viper.BindPFlag("bucket", cmd.Flags().Lookup("bucket")); err != nil { 43 | return fmt.Errorf("error binding bucket: %w", err) 44 | } 45 | if err := viper.BindPFlag("privatekey", cmd.Flags().Lookup("key")); err != nil { 46 | return fmt.Errorf("error binding privatekey: %w", err) 47 | } 48 | return nil 49 | }, 50 | RunE: func(cmd *cobra.Command, args []string) error { 51 | gcpProperties := clients.NewGCPClientInit(viper.GetString("bucket"), viper.GetString("location"), "") 52 | return sign.SignAndUploadCode(gcpProperties, args[0], sbo, ro) 53 | }, 54 | } 55 | initGCPSignCodeFlags(cmd) 56 | sbo.AddFlags(cmd) 57 | ro.AddFlags(cmd) 58 | return cmd 59 | } 60 | 61 | func initGCPSignCodeFlags(cmd *cobra.Command) { 62 | cmd.Flags().StringVar(&options.Config, "config", "", "config file (default: $HOME/.fs)") 63 | cmd.Flags().String("location", "", "GCP location to perform the operation against") 64 | cmd.Flags().String("bucket", "", "cloud storage bucket to work against") 65 | cmd.Flags().String("key", "", "private key") 66 | } 67 | -------------------------------------------------------------------------------- /pkg/options/sign_image.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package options 17 | 18 | import ( 19 | "github.com/sigstore/cosign/cmd/cosign/cli/options" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | type SignOptions struct { 24 | options.SignOptions 25 | } 26 | 27 | func (o *SignOptions) AddFlags(cmd *cobra.Command) { 28 | o.Rekor.AddFlags(cmd) 29 | o.Fulcio.AddFlags(cmd) 30 | o.OIDC.AddFlags(cmd) 31 | o.SecurityKey.AddFlags(cmd) 32 | o.AnnotationOptions.AddFlags(cmd) 33 | o.Registry.AddFlags(cmd) 34 | 35 | cmd.Flags().StringVar(&o.Cert, "certificate", "", 36 | "path to the X.509 certificate in PEM format to include in the OCI Signature") 37 | 38 | cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", 39 | "path to a list of CA X.509 certificates in PEM format which will be needed "+ 40 | "when building the certificate chain for the signing certificate. "+ 41 | "Must start with the parent intermediate CA certificate of the "+ 42 | "signing certificate and end with the root certificate. Included in the OCI Signature") 43 | 44 | cmd.Flags().BoolVar(&o.Upload, "upload", true, 45 | "whether to upload the signature") 46 | 47 | cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", 48 | "write the signature to FILE") 49 | 50 | cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", 51 | "write the certificate to FILE") 52 | 53 | cmd.Flags().StringVar(&o.PayloadPath, "payload", "", 54 | "path to a payload file to use rather than generating one") 55 | 56 | cmd.Flags().BoolVarP(&o.Force, "force", "f", false, 57 | "skip warnings and confirmations") 58 | 59 | cmd.Flags().BoolVarP(&o.Recursive, "recursive", "r", false, 60 | "if a multi-arch image is specified, additionally sign each discrete image") 61 | 62 | cmd.Flags().StringVar(&o.Attachment, "attachment", "", 63 | "related image attachment to sign (sbom), default none") 64 | 65 | cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, 66 | "skip confirmation prompts for non-destructive operations") 67 | 68 | cmd.Flags().BoolVar(&o.NoTlogUpload, "no-tlog-upload", false, 69 | "whether to not upload the transparency log") 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yaml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | 3 | on: 4 | workflow_run: 5 | workflows: [Pre-test] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | if: > 13 | github.event.push.branch == 'main' || 14 | (github.event.workflow_run.event == 'pull_request_review' && 15 | github.event.workflow_run.conclusion == 'success') 16 | permissions: 17 | id-token: write 18 | steps: 19 | - name: Harden Runner 20 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 21 | with: 22 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 23 | 24 | - name: Checkout 25 | uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 26 | - name: Set up Go 27 | uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 28 | with: 29 | go-version: 1.19 30 | check-latest: true 31 | cache: true 32 | 33 | - name: Install required packages 34 | run: npm install @actions/core@1.6.0 @actions/http-client uuid@^3.3.3 35 | 36 | - name: Generate uuid 37 | uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 38 | id: get_uuid 39 | with: 40 | script: | 41 | const coredemo = require('@actions/core') 42 | const uuid = require('uuid/v1') 43 | let uuidVal = uuid() 44 | coredemo.setOutput('uuid', uuidVal) 45 | 46 | - name: Test 47 | env: 48 | ACCESS_KEY: ${{ secrets.ACCESS_KEY }} 49 | SECRET_KEY: ${{ secrets.SECRET_KEY }} 50 | BUCKET: ${{ secrets.BUCKET }} 51 | REGION: ${{ secrets.REGION }} 52 | FUNCTION_REGION: ${{ secrets.FUNCTION_REGION }} 53 | COSIGN_EXPERIMENTAL: 0 54 | is_start: true 55 | uuid: ${{ steps.get_uuid.outputs.uuid }} 56 | run: test/e2e_test.sh 57 | 58 | - name: Get IdToken 59 | if: always() 60 | uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 61 | id: get_id_token 62 | with: 63 | script: | 64 | const coredemo = require('@actions/core') 65 | let id_token = await coredemo.getIDToken("sigstore") 66 | coredemo.setOutput('id_token', id_token) 67 | 68 | - name: KeylessTest 69 | if: always() 70 | env: 71 | ACCESS_KEY: ${{ secrets.ACCESS_KEY }} 72 | SECRET_KEY: ${{ secrets.SECRET_KEY }} 73 | BUCKET: ${{ secrets.BUCKET }} 74 | REGION: ${{ secrets.REGION }} 75 | FUNCTION_REGION: ${{ secrets.FUNCTION_REGION }} 76 | jwt_token: ${{ steps.get_id_token.outputs.id_token }} 77 | COSIGN_EXPERIMENTAL: 1 78 | is_start: false 79 | uuid: ${{ steps.get_uuid.outputs.uuid }} 80 | run: test/e2e_test_keyless.sh -------------------------------------------------------------------------------- /cmd/function-clarity/cli/sign/sign.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package sign 17 | 18 | import ( 19 | "fmt" 20 | "github.com/openclarity/functionclarity/pkg/utils" 21 | 22 | "github.com/google/uuid" 23 | "github.com/openclarity/functionclarity/pkg/integrity" 24 | o "github.com/openclarity/functionclarity/pkg/options" 25 | "github.com/sigstore/cosign/cmd/cosign/cli/generate" 26 | "github.com/sigstore/cosign/cmd/cosign/cli/options" 27 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 28 | "github.com/sigstore/cosign/cmd/cosign/cli/sign" 29 | "github.com/spf13/viper" 30 | ) 31 | 32 | func SignIdentity(identity string, o *o.SignBlobOptions, ro *co.RootOptions, isKeyless bool) (string, error) { 33 | path := utils.FunctionClarityHomeDir + uuid.New().String() 34 | if err := integrity.SaveTextToFile(identity, path); err != nil { 35 | return "", fmt.Errorf("signing identity: %w", err) 36 | } 37 | 38 | oidcClientSecret, err := o.OIDC.ClientSecret() 39 | if err != nil { 40 | return "", fmt.Errorf("signing identity: %w", err) 41 | } 42 | ko := options.KeyOpts{ 43 | KeyRef: viper.GetString("privatekey"), 44 | PassFunc: generate.GetPass, 45 | Sk: o.SecurityKey.Use, 46 | Slot: o.SecurityKey.Slot, 47 | FulcioURL: o.Fulcio.URL, 48 | IDToken: o.Fulcio.IdentityToken, 49 | InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, 50 | RekorURL: o.Rekor.URL, 51 | OIDCIssuer: o.OIDC.Issuer, 52 | OIDCClientID: o.OIDC.ClientID, 53 | OIDCClientSecret: oidcClientSecret, 54 | OIDCRedirectURL: o.OIDC.RedirectURL, 55 | OIDCDisableProviders: o.OIDC.DisableAmbientProviders, 56 | BundlePath: o.BundlePath, 57 | SkipConfirmation: o.SkipConfirmation, 58 | } 59 | outputSignature := o.OutputSignature 60 | outputCertificate := o.OutputCertificate 61 | if isKeyless { 62 | outputSignature = utils.FunctionClarityHomeDir + identity + ".sig" 63 | outputCertificate = utils.FunctionClarityHomeDir + identity + ".crt.base64" 64 | } 65 | 66 | sig, err := sign.SignBlobCmd(ro, ko, o.Registry, path, o.Base64Output, outputSignature, outputCertificate) 67 | 68 | if err != nil { 69 | return "", fmt.Errorf("signing identity: %w", err) 70 | } 71 | 72 | return string(sig), nil 73 | 74 | } 75 | -------------------------------------------------------------------------------- /pkg/utils/file.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package utils 17 | 18 | import ( 19 | "archive/zip" 20 | "fmt" 21 | "io" 22 | "net/http" 23 | "os" 24 | "path/filepath" 25 | "strings" 26 | ) 27 | 28 | func DownloadFile(fileName string, url *string) error { 29 | 30 | // Get the data 31 | resp, err := http.Get(*url) 32 | if err != nil { 33 | return err 34 | } 35 | defer resp.Body.Close() 36 | 37 | // Create the file 38 | out, err := os.Create(FunctionClarityHomeDir + fileName) 39 | if err != nil { 40 | return err 41 | } 42 | defer out.Close() 43 | 44 | // Write the body to file 45 | _, err = io.Copy(out, resp.Body) 46 | return err 47 | } 48 | 49 | func ExtractZip(zipPath string, dstToExtract string) error { 50 | 51 | archive, err := zip.OpenReader(zipPath) 52 | if err != nil { 53 | return fmt.Errorf("failed to open archive file : %s. %v", zipPath, err) 54 | } 55 | defer archive.Close() 56 | 57 | for _, f := range archive.File { 58 | filePath := filepath.Join(dstToExtract, f.Name) 59 | 60 | if !strings.HasPrefix(filePath, filepath.Clean(dstToExtract)+string(os.PathSeparator)) { 61 | return fmt.Errorf("invalid file path") 62 | } 63 | if f.FileInfo().IsDir() { 64 | if err := os.MkdirAll(filePath, os.ModePerm); err != nil { 65 | return fmt.Errorf("failed to create directory for path: %s. %v", filePath, err) 66 | } 67 | continue 68 | } 69 | 70 | if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { 71 | return fmt.Errorf("failed to create directories for path: %s. %v", filePath, err) 72 | } 73 | 74 | dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) 75 | if err != nil { 76 | return fmt.Errorf("failed to open destination file for writing: %s. %v", filePath, err) 77 | } 78 | 79 | fileInArchive, err := f.Open() 80 | if err != nil { 81 | return fmt.Errorf("failed to open file in archive : %s. %v", f.Name, err) 82 | } 83 | 84 | if _, err := io.Copy(dstFile, fileInArchive); err != nil { 85 | return fmt.Errorf("failed to copy file: %s from archive to local path: %s. %v", f.Name, dstFile.Name(), err) 86 | } 87 | 88 | dstFile.Close() 89 | fileInArchive.Close() 90 | } 91 | return nil 92 | } 93 | 94 | func CleanDirectory(directory string) { 95 | if err := os.RemoveAll(directory); err != nil { 96 | fmt.Printf("failed to delete directory %v: %v", directory, err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/gcp/gcp.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package gcp 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/common" 22 | opt "github.com/openclarity/functionclarity/cmd/function-clarity/cli/options" 23 | "github.com/openclarity/functionclarity/pkg/clients" 24 | "github.com/openclarity/functionclarity/pkg/options" 25 | "github.com/openclarity/functionclarity/pkg/verify" 26 | "github.com/spf13/cobra" 27 | "github.com/spf13/viper" 28 | ) 29 | 30 | func GcpSign() *cobra.Command { 31 | cmd := &cobra.Command{ 32 | Use: "gcp", 33 | Short: "sign code/image and upload to GCP", 34 | } 35 | cmd.AddCommand(GCPSignCode()) 36 | cmd.AddCommand(common.SignImage()) 37 | return cmd 38 | } 39 | 40 | func GcpVerify() *cobra.Command { 41 | o := &options.VerifyOpts{} 42 | var functionRegion string 43 | cmd := &cobra.Command{ 44 | Use: "gcp", 45 | Short: "verify function identity", 46 | Args: cobra.ExactArgs(1), 47 | PreRunE: func(cmd *cobra.Command, args []string) error { 48 | if err := viper.BindPFlag("location", cmd.Flags().Lookup("location")); err != nil { 49 | return fmt.Errorf("error binding location: %w", err) 50 | } 51 | if err := viper.BindPFlag("bucket", cmd.Flags().Lookup("bucket")); err != nil { 52 | return fmt.Errorf("error binding bucket: %w", err) 53 | } 54 | if err := viper.BindPFlag("publickey", cmd.Flags().Lookup("key")); err != nil { 55 | return fmt.Errorf("error binding publickey: %w", err) 56 | } 57 | return nil 58 | }, 59 | RunE: func(cmd *cobra.Command, args []string) error { 60 | o.Key = viper.GetString("publickey") 61 | gcpClient := clients.NewGCPClientInit(viper.GetString("bucket"), viper.GetString("location"), functionRegion) 62 | _, _, err := verify.Verify(gcpClient, args[0], o, cmd.Context(), "", "", nil, nil, "", "") 63 | return err 64 | }, 65 | } 66 | cmd.Flags().StringVar(&functionRegion, "function-location", "", "GCP location where the verified function runs") 67 | cmd.MarkFlagRequired("function-region") //nolint:errcheck 68 | o.AddFlags(cmd) 69 | initGCPVerifyFlags(cmd) 70 | return cmd 71 | } 72 | 73 | func initGCPVerifyFlags(cmd *cobra.Command) { 74 | cmd.Flags().StringVar(&opt.Config, "config", "", "config file (default: $HOME/.fs)") 75 | cmd.Flags().String("location", "", "GCP location to perform the operation against") 76 | cmd.Flags().String("bucket", "", "GCP bucket to work against") 77 | cmd.Flags().String("key", "", "public key") 78 | } 79 | -------------------------------------------------------------------------------- /FunctionClarity Diagram.drawio: -------------------------------------------------------------------------------- 1 | 5Vxbc+I4Fv41VO0+QPkK+BGckJ2p9G520r0zvS+UsAVoYiyPLALsr1/JlnwV4AQb6BkeOtKxJcvnpvOdI3fPdDf7JwKi9Rfsw6BnaP6+Zz70DEMfjwz2h1MOKcUe2ylhRZAvbsoJr+h/UBA1Qd0iH8alGynGAUVRmejhMIQeLdEAIXhXvm2Jg/JTI7CCNcKrB4I69Vfk03VKHdtaTv8HRKu1fLKuiSsbIG8WhHgNfLwrkMzHnukSjGna2uxdGHDmSb6k42ZHrmYLIzCkTQa82do/vz6Rt982P0c/f/nPLwt7vewL6byDYCteWCyWHiQHCN6GPuST6D1zulsjCl8j4PGrOyZzRlvTTSAuL1EQuDjAJBlrzpIfo9cXK9b/DgmF+wJJLP4J4g2k5MBuEVf7Y8FIoUm6Jfq7XC62VJt1QSbmUBCB0IVVNnfOLtYQHPsI98xu2ee6j3bCvpgS/AbllRCHsCWe6naJp+aoztOMti7reUc81Yc1FkKfmaToYkLXeIVDEDzm1GnOZI318nueMY4Ea3+HlB6EfwFbisuMh3tEfyu0v/OpBrboPezFzEnnIDpH+R/jLfHgiXcUTKeArCA9xQuhX5wBJ8VJYAAoei/7rNZFYyu0fQg2XIfDRcz/zLahRxEO3QAQRNkThwF7v+mCsNaKt9znn2riZY4x4s2YQj5HBAli64VEkF7y/nnz2UO5h5wypxYsJzMAYTmGwhvphsJyxp0Zzui8M7qCJX3eKnSjoVnYd2UVctkFvr9CwjQpgHHc4ypQNQIKY4rC1e0M4XE4m7luS1uIcXeGUN+VzwjEh1GAD7eTB5PGyB21Iw/DtO5NHuPzjkmyGm2SeHzK3xexKPwZLGDwgmPEdxV2fYEpxRt2Q8AvTIH3tko8lmSmD5dgm0hXzjAJ0IqPpNx/TUEcpSghkQgjJA+cSKomKay9ppRjjAl/eWNGYDzwArz1UQjIYeCxRRizYLtBK9yPCPa3yb7HaLv5mEMAdz23kr/efIkoo/sRmSfe0nB3svFH2jAn0MN8aNTfwQV7V8g6hsaFNNN0Pha+4yju687Y2DuO3tcHEXMfraiLUwmqbYW2DK+pLVI1C9ryk8/eTxlOcOBHIEhZfzNvartuW2GFMRpUInKnLhDHrstj1Jk89Jo8XrlF1WSBMin9GQShG8bdSaIeaHxj+xbwlXtazIQE6JbAP4c8DP3+LMNqvq8tA7if8HQUB5GhL5oPXgDiGHllrpZDcMYycuDQVMBR3v1evJaD06Qn0alEtDmK/d7L8a0a0SrTDB8GUa3BX6Mp/C1mgBR7laQ1xgPiCS8YsVfLVDDLI0kF1Cqalb64GFXMxFUmsqrY0a5MlHKmNlGipdlrX6C4KiB/SdqqrDc9zhn+6zYfaGtlLioiF2U60Da7cgjmTQD3Z1JXSxxS6bfHndi4VKnzRj5uaOTXQfPW8LxtcMgRNdfgrDgBFnIGTf2KUrMbQGprpNjqDM3sii31KPAyl3Fhpvu07M77EMHZodmMsVmtoX3G1oM6Gcv56F0Gc18JWq0g4ZEeRzqKcE9SOFrMxymmqqZsP97XliLLW0Jf2rEV3GH0ean2yH28HJDqel2bdD2DEW3HpE9D/+uX4b+m0TaePf9OXh/jf/f7Dcz0Jjngkxw/u5lYZsPNxLyzzURVH6xZpAsCb8uWw5Ca9gw2Cx9cZuGoki756xikbldBosomrwoSrTpInEHqrfmzQj+9gPxU+PcN2VuSEYtu7k5GysJm1axecEwvM0whanTpFg7E7vtXM2/TujvzNlW1jYLACpIY/rHFQqB5qyTiOAKhcsgiq3P0vZThE76wEFEEAuWU9UJ8NaiTupM+tLOF9FMJoyWC5MxTzygy3tIAhQwayINePDjxQbzOIpWzRaMjNSHRC+CSVvIawnY2+xU/3TYAu9gcBMkWPc+C4Joh2IZjudwQ2CAfMSOooJkT2ZICLLc63SidzAkXLEdV5xl2higbHFfoGmiPKom47PBe8fiTpuBKVkBtP6ncIP9QK2IWnTp7BopiWPTrBY2Wuu4xZibXslOJNWtiJuqhcPU1ifnNXlYYzQqkaLPiNVC0YP/yLekdzn1E2Mow58MM8SeEkM6Tsukgfl+d1v3GmcLT6tTYBoZ1A9AdbSAnupK4VVaQukPuDJROmF/ox4mb4N5XH0d7pev9FnOXq7mc/cpoo4rrq6S/TX597RnsxbTJf7/98pg2n9yXvxc8drrKIy6bSaLqUc9VGASppqdV171Bvp+AVVXQUgaw7WtQVgBo5i06O39qObf3obalDbTCTy95VEt5nsAemHphyFBhcdXaSnsW1+Q0ylsC0VL1qXnbgjpHvESTrNCe9uwHleJKr1nc240jXnQTe4Cf/XgAFMyneD/nWZl51ntiMHEHDp27UueIbpw6teAMnfynW3Wha85ABsvXicyP514ae1briGcVLlV7ZVsdlyJr4WBbAWU/gGesloHaV5DjtbnUC9QVoloLbU0hRnU7v0Z1Livf94rF+7yWf6R8n8VlugIXdFCSl9/WnMuwynpFexnWZOiEEHAo3CAcaz5zpYRuVyN30y4qyNn7zfG4olDpClqts9v1bHwId716AYUp2NXRbuY/Lsa7M3tsm1Y3ePfjpwIqhzSyc/qlEt/AMa7oeWznJp7nEweACoeNHGdU8Vjj4RmflfQKwLO2x3Tju2SK/Uc7aiAB6K02pA+eJ7tEuLkuGkVl1E4qo/RGzA++VXfEC7SlaS1Reu970ZZ6YigpJCUfkmjsGcBnOEGpUckOcSYPdDSKJJAFxAIMakrI88mspVT/yyFon6nVWMb7wu/LSvhnz/zJW/ByGcNOTuGNzFuY/5XjSv0HtbZRvWz7AJNvhLRAlOh/GEOTitaKoVm6Xv6+6IZ2xj/LyT6fT2/P/xMC8/H/ -------------------------------------------------------------------------------- /cmd/function-clarity/cli/aws/aws_sign_code.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package aws 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/options" 22 | "github.com/openclarity/functionclarity/pkg/clients" 23 | o "github.com/openclarity/functionclarity/pkg/options" 24 | "github.com/openclarity/functionclarity/pkg/sign" 25 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 26 | "github.com/spf13/cobra" 27 | "github.com/spf13/viper" 28 | ) 29 | 30 | func AwsSignCode() *cobra.Command { 31 | sbo := &o.SignBlobOptions{} 32 | ro := &co.RootOptions{} 33 | 34 | cmd := &cobra.Command{ 35 | Use: "code", 36 | Short: "sign code content and upload its signature to aws", 37 | Args: cobra.ExactArgs(1), 38 | PreRunE: func(cmd *cobra.Command, args []string) error { 39 | if err := viper.BindPFlag("accessKey", cmd.Flags().Lookup("aws-access-key")); err != nil { 40 | return fmt.Errorf("error binding accessKey: %w", err) 41 | } 42 | if err := viper.BindPFlag("secretKey", cmd.Flags().Lookup("aws-secret-key")); err != nil { 43 | return fmt.Errorf("error binding secretKey: %w", err) 44 | } 45 | if err := viper.BindPFlag("region", cmd.Flags().Lookup("region")); err != nil { 46 | return fmt.Errorf("error binding region: %w", err) 47 | } 48 | if err := viper.BindPFlag("bucket", cmd.Flags().Lookup("bucket")); err != nil { 49 | return fmt.Errorf("error binding bucket: %w", err) 50 | } 51 | if err := viper.BindPFlag("privatekey", cmd.Flags().Lookup("key")); err != nil { 52 | return fmt.Errorf("error binding privatekey: %w", err) 53 | } 54 | return nil 55 | }, 56 | RunE: func(cmd *cobra.Command, args []string) error { 57 | awsClient := clients.NewAwsClient(viper.GetString("accesskey"), viper.GetString("secretkey"), viper.GetString("bucket"), viper.GetString("region"), "") 58 | return sign.SignAndUploadCode(awsClient, args[0], sbo, ro) 59 | }, 60 | } 61 | initAwsSignCodeFlags(cmd) 62 | sbo.AddFlags(cmd) 63 | ro.AddFlags(cmd) 64 | return cmd 65 | } 66 | 67 | func initAwsSignCodeFlags(cmd *cobra.Command) { 68 | cmd.Flags().StringVar(&options.Config, "config", "", "config file (default: $HOME/.fs)") 69 | cmd.Flags().String("aws-access-key", "", "aws access key") 70 | cmd.Flags().String("aws-secret-key", "", "aws secret key") 71 | cmd.Flags().String("region", "", "aws region to perform the operation against") 72 | cmd.Flags().String("bucket", "", "s3 bucket to work against") 73 | cmd.Flags().String("key", "", "private key") 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Go Binaries 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | 8 | env: 9 | CLI_PATH: ./cmd/function-clarity/ 10 | LAMBDA_PATH: ./aws_function_pkg/ 11 | 12 | jobs: 13 | release-aws-lambda: 14 | name: Release AWS Lambda 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Harden Runner 18 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 19 | with: 20 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 21 | 22 | - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 23 | 24 | - name: Set APP_VERSION env 25 | run: echo APP_VERSION=$(echo ${GITHUB_REF} | rev | cut -d'/' -f 1 | rev ) >> ${GITHUB_ENV} 26 | - name: Set BUILD_TIME env 27 | run: echo BUILD_TIME=$(date) >> ${GITHUB_ENV} 28 | - name: Environment Printer 29 | uses: managedkaos/print-env@cc44fee1591e49c86931a4a7458926ec441a85dd 30 | 31 | - uses: wangyoucao577/go-release-action@90da8ebfdc010a0e7d378419a76fd90230a05228 32 | with: 33 | goversion: https://go.dev/dl/go1.19.1.linux-amd64.tar.gz 34 | binary_name: aws_function 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | goos: linux 37 | goarch: amd64 38 | project_path: "${{ env.LAMBDA_PATH }}" 39 | build_flags: -v 40 | overwrite: TRUE 41 | asset_name: "aws_function" 42 | ldflags: -X "main.appVersion=${{ env.APP_VERSION }}" -X "main.buildTime=${{ env.BUILD_TIME }}" -X main.gitCommit=${{ github.sha }} -X main.gitRef=${{ github.ref }} 43 | 44 | release-cli: 45 | name: Release CLI 46 | needs: release-aws-lambda 47 | runs-on: ubuntu-latest 48 | strategy: 49 | matrix: 50 | goos: [ linux, windows, darwin ] 51 | goarch: [ "386", amd64 ] 52 | exclude: 53 | # windows/386 and darwin/386 seems useless 54 | - goarch: "386" 55 | goos: windows 56 | - goarch: "386" 57 | goos: darwin 58 | steps: 59 | - name: Harden Runner 60 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 61 | with: 62 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 63 | 64 | - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 65 | - name: Set APP_VERSION env 66 | run: echo APP_VERSION=$(echo ${GITHUB_REF} | rev | cut -d'/' -f 1 | rev ) >> ${GITHUB_ENV} 67 | - name: Set BUILD_TIME env 68 | run: echo BUILD_TIME=$(date) >> ${GITHUB_ENV} 69 | - name: Environment Printer 70 | uses: managedkaos/print-env@cc44fee1591e49c86931a4a7458926ec441a85dd 71 | 72 | - uses: wangyoucao577/go-release-action@90da8ebfdc010a0e7d378419a76fd90230a05228 73 | with: 74 | goversion: https://go.dev/dl/go1.19.1.linux-amd64.tar.gz 75 | github_token: ${{ secrets.GITHUB_TOKEN }} 76 | extra_files: ./run_env/utils/unified-template.template 77 | goos: ${{ matrix.goos }} 78 | goarch: ${{ matrix.goarch }} 79 | project_path: "${{ env.CLI_PATH }}" 80 | build_flags: -v 81 | overwrite: TRUE 82 | ldflags: -X "main.appVersion=${{ env.APP_VERSION }}" -X "main.buildTime=${{ env.BUILD_TIME }}" -X main.gitCommit=${{ github.sha }} -X main.gitRef=${{ github.ref }} -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecards supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '23 10 * * 6' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecards analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: Harden Runner 35 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 36 | with: 37 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 38 | 39 | - name: "Checkout code" 40 | uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0 41 | with: 42 | persist-credentials: false 43 | 44 | - name: "Run analysis" 45 | uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6 46 | with: 47 | results_file: results.sarif 48 | results_format: sarif 49 | # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: 50 | # - you want to enable the Branch-Protection check on a *public* repository, or 51 | # - you are installing Scorecards on a *private* repository 52 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 53 | # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} 54 | 55 | # Public repositories: 56 | # - Publish results to OpenSSF REST API for easy access by consumers 57 | # - Allows the repository to include the Scorecard badge. 58 | # - See https://github.com/ossf/scorecard-action#publishing-results. 59 | # For private repositories: 60 | # - `publish_results` will always be set to `false`, regardless 61 | # of the value entered here. 62 | publish_results: true 63 | 64 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 65 | # format to the repository Actions tab. 66 | - name: "Upload artifact" 67 | uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 68 | with: 69 | name: SARIF file 70 | path: results.sarif 71 | retention-days: 5 72 | 73 | # Upload the results to GitHub's code scanning dashboard. 74 | - name: "Upload to code-scanning" 75 | uses: github/codeql-action/upload-sarif@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.27 76 | with: 77 | sarif_file: results.sarif 78 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '30 0 * * 3' 22 | 23 | permissions: # added using https://github.com/step-security/secure-workflows 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: [ 'go' ] 39 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Harden Runner 44 | uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 45 | with: 46 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 47 | 48 | - name: Checkout repository 49 | uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b 50 | 51 | # Initializes the CodeQL tools for scanning. 52 | - name: Initialize CodeQL 53 | uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 54 | with: 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | 60 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 61 | # queries: security-extended,security-and-quality 62 | 63 | 64 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 65 | # If this step fails, then you should remove it and run the build manually (see below) 66 | - name: Autobuild 67 | uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 68 | 69 | # ℹ️ Command-line programs to run using the OS shell. 70 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 71 | 72 | # If the Autobuild fails above, remove it and uncomment the following three lines. 73 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 74 | 75 | # - run: | 76 | # echo "Run, Build Application using script" 77 | # ./location_of_script_within_repo/buildscript.sh 78 | 79 | - name: Perform CodeQL Analysis 80 | uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 81 | with: 82 | category: "/language:${{matrix.language}}" 83 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/common/aws_sign_image.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package common 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | 22 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/options" 23 | opt "github.com/openclarity/functionclarity/pkg/options" 24 | "github.com/sigstore/cosign/cmd/cosign/cli/generate" 25 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 26 | "github.com/sigstore/cosign/cmd/cosign/cli/sign" 27 | "github.com/spf13/cobra" 28 | "github.com/spf13/viper" 29 | ) 30 | 31 | func SignImage() *cobra.Command { 32 | o := &opt.SignOptions{} 33 | ro := &co.RootOptions{} 34 | 35 | cmd := &cobra.Command{ 36 | Use: "image", 37 | Short: "sign and upload the image digest to aws", 38 | Args: cobra.ExactArgs(1), 39 | PreRunE: func(cmd *cobra.Command, args []string) error { 40 | if err := viper.BindPFlag("privatekey", cmd.Flags().Lookup("key")); err != nil { 41 | return fmt.Errorf("error binding privatekey: %w", err) 42 | } 43 | return nil 44 | }, 45 | RunE: func(cmd *cobra.Command, args []string) error { 46 | switch o.Attachment { 47 | case "sbom", "": 48 | break 49 | default: 50 | return flag.ErrHelp 51 | } 52 | oidcClientSecret, err := o.OIDC.ClientSecret() 53 | if err != nil { 54 | return err 55 | } 56 | ko := co.KeyOpts{ 57 | KeyRef: viper.GetString("privatekey"), 58 | PassFunc: generate.GetPass, 59 | Sk: o.SecurityKey.Use, 60 | Slot: o.SecurityKey.Slot, 61 | FulcioURL: o.Fulcio.URL, 62 | IDToken: o.Fulcio.IdentityToken, 63 | InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, 64 | RekorURL: o.Rekor.URL, 65 | OIDCIssuer: o.OIDC.Issuer, 66 | OIDCClientID: o.OIDC.ClientID, 67 | OIDCClientSecret: oidcClientSecret, 68 | OIDCRedirectURL: o.OIDC.RedirectURL, 69 | OIDCDisableProviders: o.OIDC.DisableAmbientProviders, 70 | OIDCProvider: o.OIDC.Provider, 71 | SkipConfirmation: o.SkipConfirmation, 72 | } 73 | annotationsMap, err := o.AnnotationsMap() 74 | if err != nil { 75 | return err 76 | } 77 | if err := sign.SignCmd(ro, ko, o.Registry, annotationsMap.Annotations, args, o.Cert, o.CertChain, o.Upload, 78 | o.OutputSignature, o.OutputCertificate, o.PayloadPath, o.Force, o.Recursive, o.Attachment, o.NoTlogUpload); err != nil { 79 | if o.Attachment == "" { 80 | return fmt.Errorf("signing %v: %w", args, err) 81 | } 82 | return fmt.Errorf("signing attachment %s for image %v: %w", o.Attachment, args, err) 83 | } 84 | return nil 85 | }, 86 | } 87 | o.AddFlags(cmd) 88 | ro.AddFlags(cmd) 89 | initAwsSignImageFlags(cmd) 90 | return cmd 91 | } 92 | 93 | func initAwsSignImageFlags(cmd *cobra.Command) { 94 | cmd.Flags().StringVar(&options.Config, "config", "", "config file (default: $HOME/.fs)") 95 | cmd.Flags().String("key", "", "private key") 96 | } 97 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | # See https://developer.github.com/v3/repos/#edit for all available settings. 3 | 4 | # The name of the repository. Changing this will rename the repository 5 | name: functionclarity 6 | 7 | # Indicate the public archival status of the repository 8 | archived: true 9 | 10 | # A short description of the repository that will show up on GitHub 11 | description: FunctionClarity is an infrastructure solution for signing and verifying serverless functions 12 | 13 | # A URL with more information about the repository 14 | homepage: https://openclarity.io/ 15 | 16 | # Updates the default branch for this repository. 17 | default_branch: main 18 | 19 | # Either `true` to enable automated security fixes, or `false` to disable 20 | # automated security fixes. 21 | enable_automated_security_fixes: true 22 | 23 | # Either `true` to enable vulnerability alerts, or `false` to disable 24 | # vulnerability alerts. 25 | enable_vulnerability_alerts: true 26 | 27 | # See https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions for available options 28 | teams: 29 | - name: org-admins 30 | # The permission to grant the team. Can be one of: 31 | # * `pull` - can pull, but not push to or administer this repository. 32 | # * `push` - can pull and push, but not administer this repository. 33 | # * `admin` - can pull, push and administer this repository. 34 | # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions. 35 | permission: admin 36 | 37 | - name: functionclarity-admins 38 | permission: admin 39 | 40 | - name: functionclarity-maintainers 41 | permission: maintain 42 | 43 | # Collaborators: give specific users access to this repository. 44 | # See https://docs.github.com/en/rest/reference/collaborators for available options 45 | collaborators: [] 46 | 47 | branches: 48 | - name: main 49 | # https://docs.github.com/en/rest/reference/repos#update-branch-protection 50 | # Branch Protection settings. Set to null to disable 51 | protection: 52 | # Required. Require at least one approving review on a pull request, before merging. Set to null to disable. 53 | required_pull_request_reviews: 54 | # The number of approvals required. (1-6) 55 | required_approving_review_count: 1 56 | # Dismiss approved reviews automatically when a new commit is pushed. 57 | dismiss_stale_reviews: true 58 | # Blocks merge until code owners have reviewed. 59 | require_code_owner_reviews: true 60 | # Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories. 61 | dismissal_restrictions: 62 | users: [] 63 | teams: [] 64 | # Required. Require status checks to pass before merging. Set to null to disable 65 | required_status_checks: 66 | # Required. Require branches to be up to date before merging. 67 | strict: true 68 | checks: 69 | - context: build 70 | - context: verify 71 | # TODO(settings): Uncomment once https://github.com/openclarity/functionclarity/issues/69 is addressed. 72 | #- context: test 73 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. 74 | enforce_admins: true 75 | # Prevent merge commits from being pushed to matching branches 76 | required_linear_history: true 77 | # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable. 78 | restrictions: 79 | apps: [] 80 | users: [] 81 | teams: [] 82 | -------------------------------------------------------------------------------- /aws_function_pkg/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "bytes" 20 | "compress/gzip" 21 | "context" 22 | "encoding/base64" 23 | "encoding/json" 24 | "fmt" 25 | "github.com/openclarity/functionclarity/pkg/utils" 26 | "io" 27 | "log" 28 | "os" 29 | "strings" 30 | 31 | "github.com/aws/aws-lambda-go/events" 32 | "github.com/aws/aws-lambda-go/lambda" 33 | "github.com/openclarity/functionclarity/pkg/clients" 34 | i "github.com/openclarity/functionclarity/pkg/init" 35 | "github.com/openclarity/functionclarity/pkg/integrity" 36 | opts "github.com/openclarity/functionclarity/pkg/options" 37 | "github.com/openclarity/functionclarity/pkg/verify" 38 | co "github.com/sigstore/cosign/cmd/cosign/cli/options" 39 | "gopkg.in/yaml.v3" 40 | ) 41 | 42 | type RequestParameters struct { 43 | FunctionName string `json:"functionName"` 44 | ReservedConcurrentExecutions int `json:"reservedConcurrentExecutions"` 45 | } 46 | 47 | type RecordMessage struct { 48 | AwsRegion string `json:"awsRegion"` 49 | EventSource string `json:"eventSource"` 50 | EventName string `json:"eventName"` 51 | RequestParameters RequestParameters `json:"requestParameters"` 52 | } 53 | 54 | type Record struct { 55 | Message string `json:"message"` 56 | Id string `json:"id"` 57 | } 58 | 59 | type FilterRecord struct { 60 | LogEvents []Record `json:"logEvents"` 61 | MessageType string `json:"messageType"` 62 | } 63 | 64 | var config *i.AWSInput = nil 65 | 66 | func HandleRequest(context context.Context, cloudWatchEvent events.CloudwatchLogsEvent) error { 67 | filterRecord, err := extractDataFromEvent(cloudWatchEvent) 68 | if err != nil { 69 | log.Printf("Failed to extract data from event: %v", err) 70 | return fmt.Errorf("failed to extract data from event: %w", err) 71 | } 72 | recordMessage := RecordMessage{} 73 | logEvents := filterRecord.LogEvents 74 | log.Printf("logEvents: %s", logEvents) 75 | if config == nil { 76 | err := initConfig() 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | log.Printf("creating folder: %s", utils.FunctionClarityHomeDir) 82 | if err := os.MkdirAll(utils.FunctionClarityHomeDir, os.ModePerm); err != nil { 83 | return err 84 | } 85 | for logEvent := range logEvents { 86 | err = json.Unmarshal([]byte(logEvents[logEvent].Message), &recordMessage) 87 | if err != nil { 88 | log.Printf("failed to extract message from event, skipping message. %s", logEvents[logEvent].Message) 89 | continue 90 | } 91 | if shouldHandleEvent(recordMessage) { 92 | log.Printf("handling function name: %s, event name: %s, event source: %s, region: %s\n", recordMessage.RequestParameters.FunctionName, recordMessage.EventName, recordMessage.EventSource, recordMessage.AwsRegion) 93 | handleFunctionEvent(recordMessage, config.IncludedFuncTagKeys, config.IncludedFuncRegions, context) 94 | } 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func shouldHandleEvent(recordMessage RecordMessage) bool { 101 | if clients.FunctionClarityLambdaVerierName == recordMessage.RequestParameters.FunctionName || "" == recordMessage.RequestParameters.FunctionName { 102 | return false 103 | } 104 | return strings.Contains(recordMessage.EventName, "CreateFunction") || strings.Contains(recordMessage.EventName, "UpdateFunctionCode") || 105 | strings.Contains(recordMessage.EventName, "DeleteFunctionConcurrency") || (strings.Contains(recordMessage.EventName, "PutFunctionConcurrency") && recordMessage.RequestParameters.ReservedConcurrentExecutions != 0) 106 | } 107 | 108 | func handleFunctionEvent(recordMessage RecordMessage, tagKeysFilter []string, regionsFilter []string, ctx context.Context) { 109 | awsClientForDocker := clients.NewAwsClient("", "", config.Bucket, recordMessage.AwsRegion, recordMessage.AwsRegion) 110 | err := integrity.InitDocker(awsClientForDocker) 111 | if err != nil { 112 | log.Printf("Failed to init docker. %v", err) 113 | return 114 | } 115 | o := getVerifierOptions(config.IsKeyless, config.PublicKey) 116 | log.Printf("about to execute verification with post action: %s.", config.Action) 117 | awsClient := clients.NewAwsClient("", "", config.Bucket, config.Region, recordMessage.AwsRegion) 118 | _, _, err = verify.Verify(awsClient, recordMessage.RequestParameters.FunctionName, o, ctx, config.Action, config.SnsTopicArn, tagKeysFilter, regionsFilter, "", "") 119 | 120 | if err != nil { 121 | log.Printf("Failed to handle lambda result: %s, %v", recordMessage.RequestParameters.FunctionName, err) 122 | } 123 | } 124 | 125 | func initConfig() error { 126 | envConfig := os.Getenv(clients.ConfigEnvVariableName) 127 | log.Printf("config: %s", envConfig) 128 | decodedConfig, err := base64.StdEncoding.DecodeString(envConfig) 129 | if err != nil { 130 | return err 131 | } 132 | err = yaml.Unmarshal(decodedConfig, &config) 133 | if err != nil { 134 | return err 135 | } 136 | return nil 137 | } 138 | 139 | func getVerifierOptions(isKeyless bool, publicKey string) *opts.VerifyOpts { 140 | key := "cosign.pub" 141 | if isKeyless && publicKey == "" { 142 | key = "" 143 | os.Setenv(integrity.ExperimentalEnv, "1") 144 | } 145 | 146 | o := &opts.VerifyOpts{ 147 | BundlePath: "", 148 | VerifyOptions: co.VerifyOptions{ 149 | Key: key, 150 | CheckClaims: true, 151 | Attachment: "", 152 | Output: "json", 153 | SignatureRef: "", 154 | LocalImage: false, 155 | SecurityKey: co.SecurityKeyOptions{ 156 | Use: false, 157 | Slot: "", 158 | }, 159 | CertVerify: co.CertVerifyOptions{ 160 | Cert: "", 161 | CertEmail: "", 162 | CertOidcIssuer: "", 163 | CertGithubWorkflowTrigger: "", 164 | CertGithubWorkflowSha: "", 165 | CertGithubWorkflowName: "", 166 | CertGithubWorkflowRepository: "", 167 | CertGithubWorkflowRef: "", 168 | CertChain: "", 169 | EnforceSCT: false, 170 | }, 171 | Rekor: co.RekorOptions{URL: "https://rekor.sigstore.dev"}, 172 | Registry: co.RegistryOptions{ 173 | AllowInsecure: false, 174 | KubernetesKeychain: false, 175 | RefOpts: co.ReferenceOptions{}, 176 | Keychain: nil, 177 | }, 178 | SignatureDigest: co.SignatureDigestOptions{AlgorithmName: ""}, 179 | AnnotationOptions: co.AnnotationOptions{Annotations: nil}, 180 | }, 181 | } 182 | return o 183 | } 184 | 185 | func extractDataFromEvent(cloudWatchEvent events.CloudwatchLogsEvent) (*FilterRecord, error) { 186 | b64z := cloudWatchEvent.AWSLogs.Data 187 | z, err := base64.StdEncoding.DecodeString(b64z) 188 | if err != nil { 189 | return nil, err 190 | } 191 | r, err := gzip.NewReader(bytes.NewReader(z)) 192 | if err != nil { 193 | return nil, err 194 | } 195 | result, err := io.ReadAll(r) 196 | if err != nil { 197 | return nil, err 198 | } 199 | filterRecord := FilterRecord{} 200 | err = json.Unmarshal(result, &filterRecord) 201 | return &filterRecord, err 202 | } 203 | 204 | func main() { 205 | lambda.Start(HandleRequest) 206 | } 207 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/aws/init.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package aws 17 | 18 | import ( 19 | "bufio" 20 | "context" 21 | "fmt" 22 | "os" 23 | "strings" 24 | 25 | "github.com/openclarity/functionclarity/pkg/clients" 26 | i "github.com/openclarity/functionclarity/pkg/init" 27 | "github.com/sigstore/cosign/cmd/cosign/cli/generate" 28 | ) 29 | 30 | func ReceiveParameters(i *i.AWSInput) error { 31 | awsClient, err := receiveAndValidateCredentials(i) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | if err := receiveAndValidateBucketName(i, awsClient); err != nil { 37 | return err 38 | } 39 | 40 | if err := inputStringArrayParameter("enter tag keys of functions to include in the verification (leave empty to include all): ", &i.IncludedFuncTagKeys, true); err != nil { 41 | return err 42 | } 43 | if err := inputStringArrayParameter("enter the function regions to include in the verification, i.e: us-east-1,us-west-1 (leave empty to include all): ", &i.IncludedFuncRegions, true); err != nil { 44 | return err 45 | } 46 | 47 | if err := inputMultipleChoiceParameter("post verification action", &i.Action, map[string]string{"1": "detect", "2": "block"}, true); err != nil { 48 | return err 49 | } 50 | 51 | if err := receiveAndValidateSNSTopicArn(i, awsClient); err != nil { 52 | return err 53 | } 54 | 55 | if err := receiveAndValidateCloudTrail(i, awsClient); err != nil { 56 | return err 57 | } 58 | 59 | if err := inputYesNoParameter("do you want to work in keyless mode (y/n): ", &i.IsKeyless, false); err != nil { 60 | return err 61 | } 62 | 63 | if !i.IsKeyless { 64 | if err := inputKeyPair(i); err != nil { 65 | return err 66 | } 67 | } 68 | 69 | if err := digestParameters(i); err != nil { 70 | return err 71 | } 72 | return nil 73 | } 74 | 75 | func digestParameters(i *i.AWSInput) error { 76 | if i.PublicKey == "" && !i.IsKeyless { 77 | if err := generate.GenerateKeyPairCmd(context.Background(), "", []string{}); err != nil { 78 | return err 79 | } 80 | i.PublicKey = "cosign.pub" 81 | i.PrivateKey = "cosign.key" 82 | } 83 | return nil 84 | } 85 | 86 | func receiveAndValidateCloudTrail(i *i.AWSInput, awsClient *clients.AwsClient) error { 87 | if err := inputStringParameter("is there existing trail in CloudTrail (in the region selected above) which you would like to use? (if no, please press enter): ", &i.CloudTrail.Name, true); err != nil { 88 | return err 89 | } 90 | trailName := i.CloudTrail.Name 91 | if trailName != "" && !awsClient.IsCloudTrailExist(trailName) { 92 | return fmt.Errorf("validation error: SNS topic doesn't exist or you don't have permissions") 93 | } 94 | return nil 95 | } 96 | 97 | func receiveAndValidateSNSTopicArn(i *i.AWSInput, awsClient *clients.AwsClient) error { 98 | if err := inputStringParameter("enter SNS arn if you would like to be notified when signature verification fails, otherwise press enter: ", &i.SnsTopicArn, true); err != nil { 99 | return err 100 | } 101 | if i.SnsTopicArn != "" && !awsClient.IsSnsTopicExist(i.SnsTopicArn) { 102 | return fmt.Errorf("validation error: SNS topic doesn't exist or you don't have permissions") 103 | } 104 | return nil 105 | } 106 | 107 | func receiveAndValidateBucketName(i *i.AWSInput, awsClient *clients.AwsClient) error { 108 | if err := inputStringParameter("enter default bucket (you can leave empty and a bucket with name functionclarity will be created): ", &i.Bucket, true); err != nil { 109 | return err 110 | } 111 | if i.Bucket != "" && !awsClient.IsBucketExist(i.Bucket) { 112 | return fmt.Errorf("validation error: bucket doesn't exist or you don't have permissions") 113 | } 114 | return nil 115 | } 116 | 117 | func receiveAndValidateCredentials(i *i.AWSInput) (*clients.AwsClient, error) { 118 | if err := inputStringParameter("enter Access Key: ", &i.AccessKey, false); err != nil { 119 | return nil, err 120 | } 121 | if err := inputStringParameter("enter Secret Key: ", &i.SecretKey, false); err != nil { 122 | return nil, err 123 | } 124 | if err := inputStringParameter("enter region: ", &i.Region, false); err != nil { 125 | return nil, err 126 | } 127 | awsClient := clients.NewAwsClientInit(i.AccessKey, i.SecretKey, i.Region) 128 | if credentials := awsClient.ValidateCredentials(); !credentials { 129 | return nil, fmt.Errorf("validation error: credentials aren't valid") 130 | } 131 | return awsClient, nil 132 | } 133 | 134 | func inputKeyPair(i *i.AWSInput) error { 135 | if err := inputStringParameter("enter path to custom public key for code signing? (if you want us to generate key pair, please press enter): ", &i.PublicKey, true); err != nil { 136 | return err 137 | } 138 | if i.PublicKey != "" { 139 | if err := inputStringParameter("enter path to custom private key for code signing: ", &i.PrivateKey, false); err != nil { 140 | return err 141 | } 142 | } 143 | return nil 144 | } 145 | 146 | func inputStringParameter(q string, p *string, em bool) error { 147 | fmt.Print(q) 148 | reader := bufio.NewReader(os.Stdin) 149 | input, err := reader.ReadString('\n') 150 | input = strings.TrimSuffix(input, "\n") 151 | if !em && input == "" { 152 | return fmt.Errorf("this is a compulsory parameter") 153 | } 154 | *p = strings.TrimSuffix(input, "\n") 155 | return err 156 | } 157 | 158 | func inputStringArrayParameter(q string, p *[]string, em bool) error { 159 | fmt.Print(q) 160 | reader := bufio.NewReader(os.Stdin) 161 | input, err := reader.ReadString('\n') 162 | input = strings.TrimSuffix(input, "\n") 163 | input = strings.TrimSpace(input) 164 | if !em && input == "" { 165 | return fmt.Errorf("this is a compulsory parameter") 166 | } 167 | if input == "" { 168 | return nil 169 | } 170 | *p = strings.Split(input, ",") 171 | for index := range *p { 172 | (*p)[index] = strings.TrimSpace((*p)[index]) 173 | } 174 | return err 175 | } 176 | 177 | func inputYesNoParameter(q string, p *bool, em bool) error { 178 | fmt.Print(q) 179 | reader := bufio.NewReader(os.Stdin) 180 | input, err := reader.ReadString('\n') 181 | input = strings.TrimSuffix(input, "\n") 182 | if !em && input == "" { 183 | return fmt.Errorf("this is a compulsory parameter") 184 | } 185 | input = strings.ToLower(strings.TrimSpace(input)) 186 | if input == "y" { 187 | *p = true 188 | } else if input == "n" { 189 | *p = false 190 | } 191 | return err 192 | } 193 | 194 | func inputMultipleChoiceParameter(action string, p *string, m map[string]string, em bool) error { 195 | message := "select " + action + " : " 196 | for key, element := range m { 197 | message = message + "(" + key + ")" + " for " + element + "; " 198 | } 199 | if em { 200 | message = message + "leave empty for no " + action + " to perform: " 201 | } 202 | fmt.Print(message) 203 | reader := bufio.NewReader(os.Stdin) 204 | input, err := reader.ReadString('\n') 205 | if err != nil { 206 | return err 207 | } 208 | input = strings.TrimSuffix(input, "\n") 209 | if !em && input == "" { 210 | return fmt.Errorf("this is a compulsory parameter") 211 | } 212 | for key, element := range m { 213 | if input == key { 214 | *p = element 215 | } 216 | } 217 | if input == "" { 218 | if !em { 219 | return fmt.Errorf("this is a compulsory parameter") 220 | } else { 221 | *p = "" 222 | } 223 | } 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /pkg/clients/gcp_client.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package clients 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "io" 22 | "os" 23 | "strings" 24 | "time" 25 | 26 | funcv1 "cloud.google.com/go/functions/apiv1" 27 | funcpb1 "cloud.google.com/go/functions/apiv1/functionspb" 28 | funcv2 "cloud.google.com/go/functions/apiv2" 29 | funcpb2 "cloud.google.com/go/functions/apiv2/functionspb" 30 | run "cloud.google.com/go/run/apiv2" 31 | "cloud.google.com/go/run/apiv2/runpb" 32 | "cloud.google.com/go/storage" 33 | "github.com/google/uuid" 34 | "github.com/openclarity/functionclarity/pkg/utils" 35 | ) 36 | 37 | type GCPClient struct { 38 | bucket string 39 | functionRegion string 40 | } 41 | 42 | func NewGCPClientInit(bucket string, location string, functionRegion string) *GCPClient { 43 | p := new(GCPClient) 44 | p.bucket = bucket 45 | p.functionRegion = functionRegion 46 | return p 47 | } 48 | 49 | func (p *GCPClient) Upload(signature string, identity string, isKeyless bool) error { 50 | ctx := context.Background() 51 | 52 | client, err := storage.NewClient(ctx) 53 | if err != nil { 54 | return fmt.Errorf("storage.NewClient: %w", err) 55 | } 56 | defer client.Close() 57 | 58 | ctx, cancel := context.WithTimeout(ctx, time.Minute) 59 | defer cancel() 60 | 61 | o := client.Bucket(p.bucket).Object(identity + ".sig") 62 | 63 | wc := o.NewWriter(ctx) 64 | if _, err = io.Copy(wc, strings.NewReader(signature)); err != nil { 65 | return fmt.Errorf("io.Copy: %w", err) 66 | } 67 | if err := wc.Close(); err != nil { 68 | return fmt.Errorf("Writer.Close: %w", err) 69 | } 70 | fmt.Printf("Uploaded %v to: %v\n", identity+".sig", p.bucket) 71 | 72 | if isKeyless { 73 | certificatePath := utils.FunctionClarityHomeDir + identity + ".crt.base64" 74 | f, err := os.Open(certificatePath) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | o := client.Bucket(p.bucket).Object(identity + ".crt.base64") 80 | 81 | wc := o.NewWriter(ctx) 82 | if _, err = io.Copy(wc, f); err != nil { 83 | return fmt.Errorf("io.Copy: %w", err) 84 | } 85 | if err := wc.Close(); err != nil { 86 | return fmt.Errorf("Writer.Close: %w", err) 87 | } 88 | fmt.Printf("Certificate %v, uploaded to: %v\n", identity+".crt.base64", p.bucket) 89 | } 90 | return nil 91 | } 92 | 93 | func (p *GCPClient) ResolvePackageType(funcIdentifier string) (string, error) { 94 | if strings.Contains(funcIdentifier, "services") { 95 | return "Image", nil 96 | } 97 | if strings.Contains(funcIdentifier, "functions") { 98 | return "Zip", nil 99 | } 100 | 101 | return "", fmt.Errorf("function identifier doesn't match to any known package type") 102 | 103 | } 104 | 105 | func (p *GCPClient) GetFuncCode(funcIdentifier string) (string, error) { 106 | url, err := getDownloadURLFuncGen1(funcIdentifier) 107 | if err != nil { 108 | url, err = getDownloadURLFuncGen2(funcIdentifier) 109 | if err != nil { 110 | return "", fmt.Errorf("failed to get function: %w", err) 111 | } 112 | } 113 | 114 | contentName := uuid.New().String() 115 | zipFileName := contentName + ".zip" 116 | defer utils.CleanDirectory(utils.FunctionClarityHomeDir + zipFileName) 117 | 118 | if err := utils.DownloadFile(contentName+".zip", &url); err != nil { 119 | return "", err 120 | } 121 | if err := utils.ExtractZip(utils.FunctionClarityHomeDir+zipFileName, utils.FunctionClarityHomeDir+contentName); err != nil { 122 | return "", err 123 | } 124 | return utils.FunctionClarityHomeDir + contentName, nil 125 | } 126 | 127 | func getDownloadURLFuncGen1(funcIdentifier string) (string, error) { 128 | ctx := context.Background() 129 | client, err := funcv1.NewCloudFunctionsClient(ctx) 130 | if err != nil { 131 | return "", fmt.Errorf("cloud functions.NewClient: %w", err) 132 | } 133 | defer client.Close() 134 | 135 | downloadUrl, err := client.GenerateDownloadUrl(ctx, &funcpb1.GenerateDownloadUrlRequest{Name: funcIdentifier}) 136 | if err != nil { 137 | return "", err 138 | } 139 | return downloadUrl.DownloadUrl, err 140 | } 141 | 142 | func getDownloadURLFuncGen2(funcIdentifier string) (string, error) { 143 | ctx := context.Background() 144 | client, err := funcv2.NewFunctionClient(ctx) 145 | if err != nil { 146 | return "", fmt.Errorf("cloud functions.NewClient: %w", err) 147 | } 148 | defer client.Close() 149 | 150 | downloadUrl, err := client.GenerateDownloadUrl(ctx, &funcpb2.GenerateDownloadUrlRequest{Name: funcIdentifier}) 151 | if err != nil { 152 | return "", err 153 | } 154 | return downloadUrl.DownloadUrl, nil 155 | } 156 | 157 | func (p *GCPClient) GetFuncImageURI(funcIdentifier string) (string, error) { 158 | ctx := context.Background() 159 | client, err := run.NewServicesClient(ctx) 160 | if err != nil { 161 | return "", fmt.Errorf("cloud run.NewClient: %w", err) 162 | } 163 | defer client.Close() 164 | 165 | service, err := client.GetService(ctx, &runpb.GetServiceRequest{Name: funcIdentifier}) 166 | if err != nil { 167 | return "", err 168 | } 169 | if len(service.Annotations) > 1 { 170 | return "", fmt.Errorf("there are more than one image connected to service: %v\n", funcIdentifier) 171 | } 172 | for _, a := range service.Annotations { 173 | return a, nil 174 | } 175 | return "", fmt.Errorf("there are no image connected to service: %v\n", funcIdentifier) 176 | } 177 | 178 | func (p *GCPClient) IsFuncInRegions(regions []string) bool { 179 | panic("not yet supported") 180 | } 181 | 182 | func (p *GCPClient) FuncContainsTags(funcIdentifier string, tagKes []string) (bool, error) { 183 | panic("not yet supported") 184 | } 185 | 186 | func (p *GCPClient) DownloadSignature(fileName string, outputType string, bucketPathToSignatures string) error { 187 | ctx := context.Background() 188 | client, err := storage.NewClient(ctx) 189 | if err != nil { 190 | return fmt.Errorf("storage.NewClient: %v", err) 191 | } 192 | defer client.Close() 193 | 194 | ctx, cancel := context.WithTimeout(ctx, time.Minute) 195 | defer cancel() 196 | 197 | outputFile := utils.FunctionClarityHomeDir + fileName + "." + outputType 198 | f, err := os.Create(outputFile) 199 | if err != nil { 200 | return fmt.Errorf("os.Create: %v", err) 201 | } 202 | 203 | objectName := fileName + "." + outputType 204 | rc, err := client.Bucket(p.bucket).Object(objectName).NewReader(ctx) 205 | if err != nil { 206 | return fmt.Errorf("Object(%q).NewReader: %v", objectName, err) 207 | } 208 | defer rc.Close() 209 | 210 | if _, err := io.Copy(f, rc); err != nil { 211 | return fmt.Errorf("io.Copy: %v", err) 212 | } 213 | if err = f.Close(); err != nil { 214 | return fmt.Errorf("f.Close: %v", err) 215 | } 216 | fmt.Printf("Downloaded %v to: %v\n", objectName, outputFile) 217 | return nil 218 | } 219 | 220 | func (p *GCPClient) HandleBlock(funcIdentifier *string, failed bool) error { 221 | panic("not yet supported") 222 | } 223 | 224 | func (p *GCPClient) HandleDetect(funcIdentifier *string, failed bool) error { 225 | panic("not yet supported") 226 | } 227 | 228 | func (p *GCPClient) Notify(msg string, snsArn string) error { 229 | panic("not yet supported") 230 | } 231 | 232 | func (p *GCPClient) FillNotificationDetails(notification *Notification, functionIdentifier string) error { 233 | panic("not yet supported") 234 | } 235 | 236 | func (o *GCPClient) DownloadPublicKeys(path string) (string, error) { 237 | panic("not yet supported") 238 | } 239 | 240 | func (o *GCPClient) GetFuncHash(funcIdentifier string) (string, error) { 241 | panic("not yet supported") 242 | } 243 | -------------------------------------------------------------------------------- /run_env/utils/unified-template.template: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "This stack grants permission through a IAM Role to provide comprehensive serverless security to the AWS Account.", 3 | "Resources": { 4 | "FunctionClarityLambdaVerifier": { 5 | "Type": "AWS::Lambda::Function", 6 | "Properties": { 7 | "Code": { 8 | "S3Bucket": "{{.bucketName}}", 9 | "S3Key": "function-clarity.zip" 10 | }, 11 | "Description": "Function clarity function", 12 | "Environment": { 13 | "Variables": { 14 | "FUNCTION_CLARITY_BUCKET": "{{.bucketName}}", 15 | "HOME": "/tmp", 16 | "CONFIGURATION": "{{.config}}" 17 | } 18 | }, 19 | "FunctionName": "FunctionClarityLambda{{.suffix}}", 20 | "Handler": "function-clarity", 21 | "PackageType": "Zip", 22 | "MemorySize": 1024, 23 | "ReservedConcurrentExecutions": 5, 24 | "Role": { 25 | "Fn::GetAtt": [ 26 | "FunctionClarityLambdaRole", 27 | "Arn" 28 | ] 29 | }, 30 | "Runtime": "go1.x", 31 | "Timeout" : 60 32 | } 33 | }, 34 | "FunctionClarityLambdaRole": { 35 | "Type": "AWS::IAM::Role", 36 | "Properties": { 37 | "Path": "/", 38 | "AssumeRolePolicyDocument": { 39 | "Version": "2012-10-17", 40 | "Statement": [ 41 | { 42 | "Effect": "Allow", 43 | "Principal": { 44 | "Service": "lambda.amazonaws.com" 45 | }, 46 | "Action": "sts:AssumeRole" 47 | } 48 | ] 49 | }, 50 | "Policies": [ 51 | { 52 | "PolicyName": "FunctionClarityLambdaPolicy", 53 | "PolicyDocument": { 54 | "Version": "2012-10-17", 55 | "Statement": [ 56 | { 57 | "Effect": "Allow", 58 | "Action": [ 59 | "s3:Get*", 60 | "s3:List*", 61 | "lambda:GetFunction", 62 | "lambda:PutFunctionConcurrency", 63 | "lambda:GetFunctionConcurrency", 64 | "lambda:DeleteFunctionConcurrency", 65 | "lambda:TagResource", 66 | "lambda:UnTagResource", 67 | "lambda:ListTags", 68 | "logs:*", 69 | "kms:Get*", 70 | "ecr:GetAuthorizationToken", 71 | "ecr:BatchGetImage", 72 | "ecr:GetDownloadUrlForLayer", 73 | "sns:Publish" 74 | ], 75 | "Resource": "*" 76 | } 77 | ] 78 | } 79 | } 80 | ] 81 | } 82 | }, 83 | {{if .withTrail -}} 84 | "FunctionClarityLogGroup": { 85 | "Type": "AWS::Logs::LogGroup", 86 | "DependsOn": "FunctionClarityLambdaVerifier", 87 | "Properties": { 88 | "LogGroupName": "FunctionClarityMonitoringLogGroup", 89 | "RetentionInDays": 1 90 | } 91 | },{{- end}} 92 | "FunctionClarityLogGroupLambdaPermissions": { 93 | "Type": "AWS::Lambda::Permission", 94 | {{if .withTrail -}}"DependsOn": "FunctionClarityLogGroup",{{- else}} "DependsOn": "FunctionClarityLambdaVerifier",{{- end}} 95 | "Properties" : { 96 | "FunctionName": "FunctionClarityLambda{{.suffix}}", 97 | "Action" : "lambda:InvokeFunction", 98 | "Principal": { "Fn::Sub": "logs.${AWS::Region}.amazonaws.com"}, 99 | "SourceArn": {{if .withTrail -}} 100 | { 101 | "Fn::GetAtt": [ 102 | "FunctionClarityLogGroup", 103 | "Arn" 104 | ] 105 | } {{- else }} "{{.logGroupArn}}" 106 | {{- end}} 107 | } 108 | }, 109 | "FunctionClarityLogGroupFilter": { 110 | "Type": "AWS::Logs::SubscriptionFilter", 111 | "DependsOn": "FunctionClarityLogGroupLambdaPermissions", 112 | "Properties": { 113 | "DestinationArn": { 114 | "Fn::GetAtt": [ 115 | "FunctionClarityLambdaVerifier", 116 | "Arn" 117 | ] 118 | }, 119 | "FilterPattern": "{ $.eventSource=lambda.amazonaws.com && ( $.eventName=CreateFunction* || $.eventName=UpdateFunctionCode* || $.eventName=DeleteFunctionConcurrency* || $.eventName=PutFunctionConcurrency*)}", 120 | "LogGroupName": {{if .withTrail -}} "FunctionClarityMonitoringLogGroup" {{- else }} "{{.logGroupName}}" {{- end}} 121 | } 122 | }{{if .withTrail -}}, 123 | "FunctionClarityTrailBucket": { 124 | "Type": "AWS::S3::Bucket", 125 | "Properties": { 126 | "LifecycleConfiguration": { 127 | "Rules": [ 128 | { 129 | "ExpirationInDays": 1, 130 | "Status": "Enabled" 131 | } 132 | ] 133 | } 134 | } 135 | }, 136 | "FunctionClarityTrailBucketPolicy": { 137 | "Type": "AWS::S3::BucketPolicy", 138 | "Properties": { 139 | "Bucket": { 140 | "Ref": "FunctionClarityTrailBucket" 141 | }, 142 | "PolicyDocument": { 143 | "Version": "2012-10-17", 144 | "Statement": [ 145 | { 146 | "Effect": "Allow", 147 | "Principal": { 148 | "Service": "cloudtrail.amazonaws.com" 149 | }, 150 | "Action": "s3:GetBucket*", 151 | "Resource": { 152 | "Fn::Sub": "arn:aws:s3:::${FunctionClarityTrailBucket}" 153 | } 154 | }, 155 | { 156 | "Effect": "Allow", 157 | "Principal": { 158 | "Service": "cloudtrail.amazonaws.com" 159 | }, 160 | "Action": "s3:PutObject", 161 | "Resource": { 162 | "Fn::Sub": "arn:aws:s3:::${FunctionClarityTrailBucket}/AWSLogs/${AWS::AccountId}/*" 163 | } 164 | } 165 | ] 166 | } 167 | } 168 | }, 169 | "FunctionClarityCloudTrailToCloudWatchLogsRole": { 170 | "Type": "AWS::IAM::Role", 171 | "Properties": { 172 | "Path": "/", 173 | "AssumeRolePolicyDocument": { 174 | "Version": "2012-10-17", 175 | "Statement": [ 176 | { 177 | "Effect": "Allow", 178 | "Principal": { 179 | "Service": [ 180 | "cloudtrail.amazonaws.com" 181 | ] 182 | }, 183 | "Action": [ 184 | "sts:AssumeRole" 185 | ] 186 | } 187 | ] 188 | }, 189 | "Policies": [ 190 | { 191 | "PolicyName": "FunctionClarity-cloudtrail-to-cloudwatchlogs-policy", 192 | "PolicyDocument": { 193 | "Version": "2012-10-17", 194 | "Statement": [ 195 | { 196 | "Effect": "Allow", 197 | "Action": [ 198 | "logs:PutLogEvents", 199 | "logs:CreateLogStream" 200 | ], 201 | "Resource": { 202 | "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:FunctionClarityMonitoringLogGroup:log-stream:*" 203 | } 204 | } 205 | ] 206 | } 207 | } 208 | ] 209 | } 210 | }, 211 | "FunctionClarityCloudTrail": { 212 | "Type": "AWS::CloudTrail::Trail", 213 | "DependsOn": [ 214 | "FunctionClarityTrailBucketPolicy" 215 | ], 216 | "Properties": { 217 | "IsLogging": true, 218 | "IsMultiRegionTrail": true, 219 | "IncludeGlobalServiceEvents": true, 220 | "CloudWatchLogsLogGroupArn": { 221 | "Fn::GetAtt": [ 222 | "FunctionClarityLogGroup", 223 | "Arn" 224 | ] 225 | }, 226 | "CloudWatchLogsRoleArn": { 227 | "Fn::GetAtt": [ 228 | "FunctionClarityCloudTrailToCloudWatchLogsRole", 229 | "Arn" 230 | ] 231 | }, 232 | "S3BucketName": { 233 | "Ref": "FunctionClarityTrailBucket" 234 | }, 235 | "TrailName": "FunctionClarityTrail", 236 | "EventSelectors": [ 237 | { 238 | "ReadWriteType": "WriteOnly" 239 | } 240 | ] 241 | } 242 | } 243 | {{- end}} 244 | } 245 | } -------------------------------------------------------------------------------- /pkg/verify/verifier.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package verify 17 | 18 | import ( 19 | "context" 20 | "encoding/json" 21 | "errors" 22 | "fmt" 23 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/verify" 24 | "github.com/openclarity/functionclarity/pkg/utils" 25 | "io" 26 | "os" 27 | "path/filepath" 28 | "strings" 29 | 30 | "github.com/openclarity/functionclarity/pkg/clients" 31 | "github.com/openclarity/functionclarity/pkg/integrity" 32 | "github.com/openclarity/functionclarity/pkg/options" 33 | v "github.com/sigstore/cosign/cmd/cosign/cli/verify" 34 | ) 35 | 36 | func Verify(client clients.Client, functionIdentifier string, o *options.VerifyOpts, ctx context.Context, action string, 37 | topicArn string, tagKeysFilter []string, filteredRegions []string, pathToPublicKeys string, pathToSignatures string) (string, bool, error) { 38 | 39 | if filteredRegions != nil && (len(filteredRegions) > 0) { 40 | funcInRegions := client.IsFuncInRegions(filteredRegions) 41 | if !funcInRegions { 42 | fmt.Printf("function: %s not in regions list: %s, skipping validation", functionIdentifier, filteredRegions) 43 | return "", false, nil 44 | } 45 | } 46 | 47 | if tagKeysFilter != nil && (len(tagKeysFilter) > 0) { 48 | funcContainsTag, err := client.FuncContainsTags(functionIdentifier, tagKeysFilter) 49 | if err != nil { 50 | return "", false, fmt.Errorf("check function tags: failed to check tags of function: %s: %w", functionIdentifier, err) 51 | } 52 | if !funcContainsTag { 53 | fmt.Printf("function: %s doesn't contain tag in the list: %s, skipping validation", functionIdentifier, tagKeysFilter) 54 | return "", false, nil 55 | } 56 | } 57 | packageType, err := client.ResolvePackageType(functionIdentifier) 58 | if err != nil { 59 | return "", false, fmt.Errorf("failed to resolve package type for function: %s: %w", functionIdentifier, err) 60 | } 61 | hash := "" 62 | switch packageType { 63 | case "Zip": 64 | hash, err = verifyCode(client, functionIdentifier, o, pathToPublicKeys, pathToSignatures, ctx) 65 | case "Image": 66 | hash, err = verifyImage(client, functionIdentifier, o, pathToPublicKeys, ctx) 67 | default: 68 | return "", false, fmt.Errorf("unsupported package type: %s for function: %s", packageType, functionIdentifier) 69 | } 70 | isVerified, err := HandleVerification(client, action, functionIdentifier, err, topicArn) 71 | return hash, isVerified, err 72 | } 73 | 74 | func HandleVerification(client clients.Client, action string, funcIdentifier string, err error, topicArn string) (bool, error) { 75 | if err != nil && !errors.Is(err, VerifyError{}) { 76 | return false, err 77 | } 78 | isVerified := err == nil 79 | 80 | fmt.Printf("verification result. verified: %t\n", isVerified) 81 | 82 | var e error 83 | switch action { 84 | case "": 85 | fmt.Printf("no action defined, nothing to do\n") 86 | case "detect": 87 | e = client.HandleDetect(&funcIdentifier, !isVerified) 88 | if e != nil { 89 | e = fmt.Errorf("handleVerification failed on function indication: %w", e) 90 | } 91 | case "block": 92 | { 93 | e = client.HandleDetect(&funcIdentifier, !isVerified) 94 | if e != nil { 95 | e = fmt.Errorf("handleVerification failed on function indication: %w", e) 96 | break 97 | } 98 | e = client.HandleBlock(&funcIdentifier, !isVerified) 99 | if e != nil { 100 | e = fmt.Errorf("handleVerification failed on function block: %w", e) 101 | break 102 | } 103 | } 104 | } 105 | 106 | if !isVerified && topicArn != "" { 107 | notification := clients.Notification{} 108 | err = client.FillNotificationDetails(¬ification, funcIdentifier) 109 | if err != nil { 110 | return false, err 111 | } 112 | notification.Action = action 113 | msg, err := json.Marshal(notification) 114 | if err != nil { 115 | return false, err 116 | } 117 | e = client.Notify(string(msg), topicArn) 118 | } 119 | 120 | return isVerified, e 121 | } 122 | 123 | func verifyImage(client clients.Client, functionIdentifier string, o *options.VerifyOpts, pathToPublicKeys string, ctx context.Context) (string, error) { 124 | funcHash, err := client.GetFuncHash(functionIdentifier) 125 | if err != nil { 126 | return "", fmt.Errorf("failed to fetch function hash for function: %s: %w", functionIdentifier, err) 127 | } 128 | imageURI, err := client.GetFuncImageURI(functionIdentifier) 129 | if err != nil { 130 | return funcHash, fmt.Errorf("failed to fetch function image URI for function: %s: %w", functionIdentifier, err) 131 | } 132 | 133 | annotations, err := o.AnnotationsMap() 134 | if err != nil { 135 | return funcHash, err 136 | } 137 | 138 | hashAlgorithm, err := o.SignatureDigest.HashAlgorithm() 139 | if err != nil { 140 | return funcHash, err 141 | } 142 | 143 | vc := v.VerifyCommand{ 144 | RegistryOptions: o.Registry, 145 | CheckClaims: o.CheckClaims, 146 | KeyRef: o.Key, 147 | CertRef: o.CertVerify.Cert, 148 | CertEmail: o.CertVerify.CertEmail, 149 | CertOidcIssuer: o.CertVerify.CertOidcIssuer, 150 | CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, 151 | CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, 152 | CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, 153 | CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, 154 | CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, 155 | CertChain: o.CertVerify.CertChain, 156 | EnforceSCT: o.CertVerify.EnforceSCT, 157 | Sk: o.SecurityKey.Use, 158 | Slot: o.SecurityKey.Slot, 159 | Output: o.Output, 160 | RekorURL: o.Rekor.URL, 161 | Attachment: o.Attachment, 162 | Annotations: annotations, 163 | HashAlgorithm: hashAlgorithm, 164 | SignatureRef: o.SignatureRef, 165 | LocalImage: o.LocalImage, 166 | } 167 | if pathToPublicKeys != "" { 168 | err = verifyMultipleKeys(client, pathToPublicKeys, o, "", ctx, false, []string{imageURI}, nil, &vc) 169 | if err != nil { 170 | return funcHash, err 171 | } 172 | } else { 173 | if err = vc.Exec(ctx, []string{imageURI}); err != nil { 174 | return funcHash, VerifyError{Err: fmt.Errorf("image verification error: %w", err)} 175 | } 176 | } 177 | return funcHash, nil 178 | } 179 | 180 | func verifyCode(client clients.Client, functionIdentifier string, o *options.VerifyOpts, pathToPublicKeys string, pathToSignatures string, ctx context.Context) (string, error) { 181 | codePath, err := client.GetFuncCode(functionIdentifier) 182 | defer utils.CleanDirectory(codePath) 183 | if err != nil { 184 | return "", fmt.Errorf("verify code: failed to fetch function code for function: %s: %w", functionIdentifier, err) 185 | } 186 | integrityCalculator := integrity.Sha256{} 187 | functionIdentity, err := integrityCalculator.GenerateIdentity(codePath) 188 | if err != nil { 189 | return "", fmt.Errorf("verify code: failed to generate function identity for function: %s: %w", functionIdentifier, err) 190 | } 191 | 192 | isKeyless := false 193 | if !o.SecurityKey.Use && o.Key == "" && o.BundlePath == "" && pathToPublicKeys == "" && integrity.IsExperimentalEnv() { 194 | isKeyless = true 195 | } 196 | if err = downloadSignatureAndCertificate(client, functionIdentifier, functionIdentity, isKeyless, pathToSignatures); err != nil { 197 | return functionIdentity, err 198 | } 199 | if pathToPublicKeys != "" { 200 | err = verifyMultipleKeys(client, pathToPublicKeys, o, functionIdentity, ctx, isKeyless, nil, verify.VerifyIdentity, nil) 201 | if err != nil { 202 | return functionIdentity, err 203 | } 204 | } else { 205 | if err = verify.VerifyIdentity(functionIdentity, o, ctx, isKeyless); err != nil { 206 | return functionIdentity, VerifyError{Err: fmt.Errorf("code verification error: %w", err)} 207 | } 208 | } 209 | 210 | return functionIdentity, nil 211 | } 212 | 213 | func verifyMultipleKeys(client clients.Client, pathToPublicKeys string, o *options.VerifyOpts, functionIdentity string, 214 | ctx context.Context, isKeyless bool, images []string, 215 | codeValidationFunc func(identity string, o *options.VerifyOpts, ctx context.Context, isKeyless bool) error, 216 | verifyCommand *v.VerifyCommand) error { 217 | 218 | publicKeysFolder, err := client.DownloadPublicKeys(pathToPublicKeys) 219 | defer utils.CleanDirectory(publicKeysFolder) 220 | if err != nil { 221 | return fmt.Errorf("code verification error: %w", err) 222 | } 223 | err = filepath.Walk(publicKeysFolder, func(path string, info os.FileInfo, err error) error { 224 | if err != nil { 225 | return err 226 | } 227 | if !info.IsDir() { 228 | if codeValidationFunc != nil { 229 | o.Key = path 230 | err = codeValidationFunc(functionIdentity, o, ctx, isKeyless) 231 | } else { 232 | verifyCommand.KeyRef = path 233 | err = verifyCommand.Exec(ctx, images) 234 | } 235 | if err == nil { 236 | return io.EOF 237 | } 238 | } 239 | return nil 240 | }) 241 | if err != nil && err != io.EOF { 242 | return VerifyError{Err: fmt.Errorf("code verification error: %w", err)} 243 | } 244 | if err == nil { 245 | return VerifyError{Err: fmt.Errorf("couldn't find valid public key")} 246 | } 247 | return nil 248 | } 249 | 250 | func downloadSignatureAndCertificate(client clients.Client, functionIdentifier string, functionIdentity string, isKeyless bool, pathToSignatures string) error { 251 | if err := client.DownloadSignature(functionIdentity, "sig", pathToSignatures); err != nil { 252 | if strings.Contains(err.Error(), utils.FunctionClaritySignatureNotFoundMessage) { 253 | return VerifyError{Err: fmt.Errorf("code verification error: %w", err)} 254 | } 255 | return fmt.Errorf("verify code: failed to get signed identity for function: %s, function idenity: %s: %w", functionIdentifier, functionIdentity, err) 256 | } 257 | if isKeyless { 258 | if err := client.DownloadSignature(functionIdentity, "crt.base64", pathToSignatures); err != nil { 259 | if strings.Contains(err.Error(), utils.FunctionClaritySignatureNotFoundMessage) { 260 | return VerifyError{Err: fmt.Errorf("code verification error: %w", err)} 261 | } 262 | return fmt.Errorf("verify code: failed to get certificate for function: %s, function idenity: %s: %w", functionIdentifier, functionIdentity, err) 263 | } 264 | } 265 | return nil 266 | } 267 | -------------------------------------------------------------------------------- /cmd/function-clarity/cli/aws/aws.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package aws 17 | 18 | import ( 19 | "fmt" 20 | "github.com/openclarity/functionclarity/pkg/utils" 21 | "os" 22 | 23 | "github.com/openclarity/functionclarity/cmd/function-clarity/cli/common" 24 | opt "github.com/openclarity/functionclarity/cmd/function-clarity/cli/options" 25 | "github.com/openclarity/functionclarity/pkg/clients" 26 | i "github.com/openclarity/functionclarity/pkg/init" 27 | "github.com/openclarity/functionclarity/pkg/options" 28 | "github.com/openclarity/functionclarity/pkg/verify" 29 | "github.com/spf13/cobra" 30 | "github.com/spf13/viper" 31 | "gopkg.in/yaml.v3" 32 | ) 33 | 34 | func AwsSign() *cobra.Command { 35 | cmd := &cobra.Command{ 36 | Use: "aws", 37 | Short: "sign code/image and upload to aws", 38 | } 39 | cmd.AddCommand(AwsSignCode()) 40 | cmd.AddCommand(common.SignImage()) 41 | return cmd 42 | } 43 | 44 | func AwsVerify() *cobra.Command { 45 | o := &options.VerifyOpts{} 46 | var lambdaRegion string 47 | cmd := &cobra.Command{ 48 | Use: "aws", 49 | Short: "verify function identity", 50 | Args: cobra.ExactArgs(1), 51 | PreRunE: func(cmd *cobra.Command, args []string) error { 52 | if err := viper.BindPFlag("accessKey", cmd.Flags().Lookup("aws-access-key")); err != nil { 53 | return fmt.Errorf("error binding accessKey: %w", err) 54 | } 55 | if err := viper.BindPFlag("secretKey", cmd.Flags().Lookup("aws-secret-key")); err != nil { 56 | return fmt.Errorf("error binding secretKey: %w", err) 57 | } 58 | if err := viper.BindPFlag("region", cmd.Flags().Lookup("region")); err != nil { 59 | return fmt.Errorf("error binding region: %w", err) 60 | } 61 | if err := viper.BindPFlag("bucket", cmd.Flags().Lookup("bucket")); err != nil { 62 | return fmt.Errorf("error binding bucket: %w", err) 63 | } 64 | if err := viper.BindPFlag("publickey", cmd.Flags().Lookup("key")); err != nil { 65 | return fmt.Errorf("error binding publickey: %w", err) 66 | } 67 | if err := viper.BindPFlag("action", cmd.Flags().Lookup("action")); err != nil { 68 | return fmt.Errorf("error binding action: %w", err) 69 | } 70 | if err := viper.BindPFlag("includedfunctagkeys", cmd.Flags().Lookup("included-func-tags")); err != nil { 71 | return fmt.Errorf("error binding action: %w", err) 72 | } 73 | if err := viper.BindPFlag("includedfuncregions", cmd.Flags().Lookup("included-func-regions")); err != nil { 74 | return fmt.Errorf("error binding action: %w", err) 75 | } 76 | if err := viper.BindPFlag("snsTopicArn", cmd.Flags().Lookup("sns-topic-arn")); err != nil { 77 | return fmt.Errorf("error binding snsTopicArn: %w", err) 78 | } 79 | return nil 80 | }, 81 | RunE: func(cmd *cobra.Command, args []string) error { 82 | o.Key = viper.GetString("publickey") 83 | awsClient := clients.NewAwsClient(viper.GetString("accesskey"), viper.GetString("secretkey"), viper.GetString("bucket"), viper.GetString("region"), lambdaRegion) 84 | _, _, err := verify.Verify(awsClient, args[0], o, cmd.Context(), viper.GetString("action"), 85 | viper.GetString("snsTopicArn"), viper.GetStringSlice("includedfunctagkeys"), viper.GetStringSlice("includedfuncregions"), 86 | "", "") 87 | return err 88 | }, 89 | } 90 | cmd.Flags().StringVar(&lambdaRegion, "function-region", "", "aws region where the verified lambda runs") 91 | cmd.MarkFlagRequired("function-region") //nolint:errcheck 92 | o.AddFlags(cmd) 93 | initAwsVerifyFlags(cmd) 94 | return cmd 95 | } 96 | 97 | func initAwsVerifyFlags(cmd *cobra.Command) { 98 | cmd.Flags().StringVar(&opt.Config, "config", "", "config file (default: $HOME/.fs)") 99 | cmd.Flags().String("aws-access-key", "", "aws access key") 100 | cmd.Flags().String("aws-secret-key", "", "aws secret key") 101 | cmd.Flags().String("region", "", "aws region to perform the operation against") 102 | cmd.Flags().String("bucket", "", "s3 bucket to work against") 103 | cmd.Flags().String("key", "", "public key") 104 | cmd.Flags().String("action", "", "action to perform upon validation result") 105 | cmd.Flags().StringSlice("included-func-tags", []string{}, "function tags to include when verifying") 106 | cmd.Flags().StringSlice("included-func-regions", []string{}, "function regions to include when verifying") 107 | cmd.Flags().String("sns-topic-arn", "", "SNS topic ARN for notifications") 108 | } 109 | 110 | func AwsInit() *cobra.Command { 111 | cmd := &cobra.Command{ 112 | Use: "aws", 113 | Short: "initialize configuration and deploy to aws", 114 | Args: cobra.NoArgs, 115 | RunE: func(cmd *cobra.Command, args []string) error { 116 | var input i.AWSInput 117 | if err := ReceiveParameters(&input); err != nil { 118 | return err 119 | } 120 | if input.Bucket == "" { 121 | input.Bucket = clients.FunctionClarityBucketName 122 | } 123 | var configForDeployment i.AWSInput 124 | configForDeployment.Bucket = input.Bucket 125 | configForDeployment.Action = input.Action 126 | configForDeployment.Region = input.Region 127 | configForDeployment.IsKeyless = input.IsKeyless 128 | configForDeployment.SnsTopicArn = input.SnsTopicArn 129 | configForDeployment.IncludedFuncTagKeys = input.IncludedFuncTagKeys 130 | configForDeployment.IncludedFuncRegions = input.IncludedFuncRegions 131 | onlyCreateConfig, err := cmd.Flags().GetBool("only-create-config") 132 | if err != nil { 133 | return err 134 | } 135 | if !onlyCreateConfig { 136 | awsClient := clients.NewAwsClientInit(input.AccessKey, input.SecretKey, input.Region) 137 | err = awsClient.DeployFunctionClarity(input.CloudTrail.Name, input.PublicKey, configForDeployment, "") 138 | if err != nil { 139 | return fmt.Errorf("failed to deploy function clarity: %w", err) 140 | } 141 | } 142 | d, err := yaml.Marshal(&input) 143 | if err != nil { 144 | return fmt.Errorf("init command fail: %w", err) 145 | } 146 | 147 | h := utils.HomeDir 148 | f, err := os.Create(h + "/.fc") 149 | if err != nil { 150 | return fmt.Errorf("init command fail: %w", err) 151 | } 152 | defer f.Close() 153 | if _, err = f.Write(d); err != nil { 154 | return fmt.Errorf("init command fail: %w", err) 155 | } 156 | return nil 157 | }, 158 | } 159 | cmd.Flags().Bool("only-create-config", false, "determine whether to only create config file without deploying") 160 | return cmd 161 | } 162 | 163 | func AwsDeploy() *cobra.Command { 164 | cmd := &cobra.Command{ 165 | Use: "aws", 166 | Short: "deploy to aws using config file", 167 | Long: "deploy to aws, this command relies on a configuration file to exist under ~/.fc, to create a config file run the command: 'init aws --only-create-config'", 168 | Args: cobra.NoArgs, 169 | RunE: func(cmd *cobra.Command, args []string) error { 170 | var configForDeployment i.AWSInput 171 | configForDeployment.Bucket = viper.GetString("bucket") 172 | configForDeployment.Action = viper.GetString("action") 173 | configForDeployment.Region = viper.GetString("region") 174 | configForDeployment.IsKeyless = viper.GetBool("iskeyless") 175 | configForDeployment.SnsTopicArn = viper.GetString("snsTopicArn") 176 | configForDeployment.IncludedFuncTagKeys = viper.GetStringSlice("includedfunctagkeys") 177 | configForDeployment.IncludedFuncRegions = viper.GetStringSlice("includedfuncregions") 178 | awsClient := clients.NewAwsClientInit(viper.GetString("accesskey"), viper.GetString("secretkey"), viper.GetString("region")) 179 | err := awsClient.DeployFunctionClarity(viper.GetString("cloudtrail.name"), viper.GetString("publickey"), configForDeployment, "") 180 | if err != nil { 181 | return fmt.Errorf("failed to deploy function clarity: %w", err) 182 | } 183 | return nil 184 | }, 185 | } 186 | return cmd 187 | } 188 | 189 | func AwsUpdateFuncConfig() *cobra.Command { 190 | cmd := &cobra.Command{ 191 | Use: "aws", 192 | Short: "update verifier function runtime configuration", 193 | Long: "update verifier function runtime configuration, the following configurations can be updated:\n" + 194 | "- included functions tags\n" + 195 | "- included functions regions\n" + 196 | "- sns topic arn\n" + 197 | "- action", 198 | Args: cobra.NoArgs, 199 | PreRunE: func(cmd *cobra.Command, args []string) error { 200 | if err := viper.BindPFlag("accessKey", cmd.Flags().Lookup("aws-access-key")); err != nil { 201 | return fmt.Errorf("error binding accessKey: %w", err) 202 | } 203 | if err := viper.BindPFlag("secretKey", cmd.Flags().Lookup("aws-secret-key")); err != nil { 204 | return fmt.Errorf("error binding secretKey: %w", err) 205 | } 206 | if err := viper.BindPFlag("region", cmd.Flags().Lookup("region")); err != nil { 207 | return fmt.Errorf("error binding region: %w", err) 208 | } 209 | if err := viper.BindPFlag("action", cmd.Flags().Lookup("action")); err != nil { 210 | return fmt.Errorf("error binding action: %w", err) 211 | } 212 | if err := viper.BindPFlag("includedfunctagkeys", cmd.Flags().Lookup("included-func-tags")); err != nil { 213 | return fmt.Errorf("error binding action: %w", err) 214 | } 215 | if err := viper.BindPFlag("includedfuncregions", cmd.Flags().Lookup("included-func-regions")); err != nil { 216 | return fmt.Errorf("error binding action: %w", err) 217 | } 218 | if err := viper.BindPFlag("snsTopicArn", cmd.Flags().Lookup("sns-topic-arn")); err != nil { 219 | return fmt.Errorf("error binding snsTopicArn: %w", err) 220 | } 221 | return nil 222 | }, 223 | RunE: func(cmd *cobra.Command, args []string) error { 224 | awsClient := clients.NewAwsClientInit(viper.GetString("accesskey"), viper.GetString("secretkey"), viper.GetString("region")) 225 | includedFuncTagKeysStringArray := viper.GetStringSlice("includedfunctagkeys") 226 | includedFuncTagKeys := &includedFuncTagKeysStringArray 227 | if !viper.IsSet("includedfunctagkeys") && !cmd.Flags().Lookup("included-func-tags").Changed { 228 | includedFuncTagKeys = nil 229 | } 230 | actionString := viper.GetString("action") 231 | action := &actionString 232 | if !viper.IsSet("action") && !cmd.Flags().Lookup("action").Changed { 233 | action = nil 234 | } 235 | includedFuncRegionsStringArray := viper.GetStringSlice("includedfuncregions") 236 | includedFuncRegions := &includedFuncRegionsStringArray 237 | if !viper.IsSet("includedfuncregions") && !cmd.Flags().Lookup("included-func-regions").Changed { 238 | includedFuncRegions = nil 239 | } 240 | topicString := viper.GetString("snsTopicArn") 241 | topic := &topicString 242 | if !viper.IsSet("snsTopicArn") && !cmd.Flags().Lookup("sns-topic-arn").Changed { 243 | topic = nil 244 | } 245 | return awsClient.UpdateVerifierFucConfig(action, includedFuncTagKeys, 246 | includedFuncRegions, topic) 247 | }, 248 | } 249 | initAwsUpdateConfigFlags(cmd) 250 | return cmd 251 | } 252 | 253 | func initAwsUpdateConfigFlags(cmd *cobra.Command) { 254 | cmd.Flags().String("aws-access-key", "", "aws access key") 255 | cmd.Flags().String("aws-secret-key", "", "aws secret key") 256 | cmd.Flags().String("region", "", "aws region where function clarity is deployed") 257 | cmd.Flags().String("action", "", "action to perform upon validation result") 258 | cmd.Flags().StringSlice("included-func-tags", []string{}, "function tags to include when verifying") 259 | cmd.Flags().StringSlice("included-func-regions", []string{}, "function regions to include when verifying") 260 | cmd.Flags().String("sns-topic-arn", "", "SNS topic ARN for notifications") 261 | } 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright (c) 2022 Cisco Systems, Inc. and its affiliates. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openclarity/functionclarity 2 | 3 | go 1.19 4 | 5 | require ( 6 | cloud.google.com/go/functions v1.9.0 7 | cloud.google.com/go/run v0.4.0 8 | cloud.google.com/go/storage v1.28.0 9 | github.com/aws/aws-lambda-go v1.35.0 10 | github.com/aws/aws-sdk-go-v2 v1.17.3 11 | github.com/aws/aws-sdk-go-v2/config v1.18.4 12 | github.com/aws/aws-sdk-go-v2/credentials v1.13.4 13 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.43 14 | github.com/aws/aws-sdk-go-v2/service/cloudformation v1.24.2 15 | github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.21.1 16 | github.com/aws/aws-sdk-go-v2/service/ecr v1.17.24 17 | github.com/aws/aws-sdk-go-v2/service/lambda v1.25.0 18 | github.com/aws/aws-sdk-go-v2/service/s3 v1.29.5 19 | github.com/aws/aws-sdk-go-v2/service/sns v1.18.5 20 | github.com/aws/aws-sdk-go-v2/service/sqs v1.19.14 21 | github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 22 | github.com/aws/smithy-go v1.13.5 23 | github.com/google/uuid v1.3.0 24 | github.com/sigstore/cosign v1.13.1 25 | github.com/spf13/cobra v1.6.1 26 | github.com/spf13/viper v1.14.0 27 | github.com/vbauerster/mpb/v5 v5.4.0 28 | gopkg.in/yaml.v3 v3.0.1 29 | ) 30 | 31 | require ( 32 | cloud.google.com/go v0.105.0 // indirect 33 | cloud.google.com/go/compute v1.12.1 // indirect 34 | cloud.google.com/go/compute/metadata v0.2.1 // indirect 35 | cloud.google.com/go/iam v0.7.0 // indirect 36 | cloud.google.com/go/longrunning v0.3.0 // indirect 37 | cuelang.org/go v0.4.3 // indirect 38 | github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect 39 | github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect 40 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 41 | github.com/Azure/go-autorest/autorest v0.11.28 // indirect 42 | github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect 43 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect 44 | github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect 45 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 46 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 47 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 48 | github.com/Microsoft/go-winio v0.6.0 // indirect 49 | github.com/OneOfOne/xxhash v1.2.8 // indirect 50 | github.com/ThalesIgnite/crypto11 v1.2.5 // indirect 51 | github.com/VividCortex/ewma v1.2.0 // indirect 52 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect 53 | github.com/agnivade/levenshtein v1.1.1 // indirect 54 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect 55 | github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect 56 | github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect 57 | github.com/alibabacloud-go/darabonba-openapi v0.2.1 // indirect 58 | github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect 59 | github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect 60 | github.com/alibabacloud-go/openapi-util v0.0.11 // indirect 61 | github.com/alibabacloud-go/tea v1.1.20 // indirect 62 | github.com/alibabacloud-go/tea-utils v1.4.5 // indirect 63 | github.com/alibabacloud-go/tea-xml v1.1.2 // indirect 64 | github.com/aliyun/credentials-go v1.2.4 // indirect 65 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect 66 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect 67 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.20 // indirect 68 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect 69 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect 70 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.27 // indirect 71 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.17 // indirect 72 | github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.19 // indirect 73 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect 74 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.21 // indirect 75 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect 76 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.20 // indirect 77 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.26 // indirect 78 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.9 // indirect 79 | github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20221027043306-dc425bc05c64 // indirect 80 | github.com/benbjohnson/clock v1.3.0 // indirect 81 | github.com/blang/semver v3.5.1+incompatible // indirect 82 | github.com/chrismellard/docker-credential-acr-env v0.0.0-20221002210726-e883f69e0206 // indirect 83 | github.com/chzyer/readline v1.5.1 // indirect 84 | github.com/clbanning/mxj/v2 v2.5.6 // indirect 85 | github.com/cockroachdb/apd/v2 v2.0.2 // indirect 86 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect 87 | github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect 88 | github.com/coreos/go-oidc/v3 v3.4.0 // indirect 89 | github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7 // indirect 90 | github.com/davecgh/go-spew v1.1.1 // indirect 91 | github.com/dimchansky/utfbom v1.1.1 // indirect 92 | github.com/docker/cli v20.10.21+incompatible // indirect 93 | github.com/docker/distribution v2.8.1+incompatible // indirect 94 | github.com/docker/docker v20.10.21+incompatible // indirect 95 | github.com/docker/docker-credential-helpers v0.7.0 // indirect 96 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 97 | github.com/emicklei/proto v1.11.0 // indirect 98 | github.com/fsnotify/fsnotify v1.6.0 // indirect 99 | github.com/ghodss/yaml v1.0.0 // indirect 100 | github.com/go-chi/chi v4.1.2+incompatible // indirect 101 | github.com/go-logr/logr v1.2.3 // indirect 102 | github.com/go-openapi/analysis v0.21.4 // indirect 103 | github.com/go-openapi/errors v0.20.3 // indirect 104 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 105 | github.com/go-openapi/jsonreference v0.20.0 // indirect 106 | github.com/go-openapi/loads v0.21.2 // indirect 107 | github.com/go-openapi/runtime v0.24.2 // indirect 108 | github.com/go-openapi/spec v0.20.7 // indirect 109 | github.com/go-openapi/strfmt v0.21.3 // indirect 110 | github.com/go-openapi/swag v0.22.3 // indirect 111 | github.com/go-openapi/validate v0.22.0 // indirect 112 | github.com/go-piv/piv-go v1.10.0 // indirect 113 | github.com/go-playground/locales v0.14.0 // indirect 114 | github.com/go-playground/universal-translator v0.18.0 // indirect 115 | github.com/go-playground/validator/v10 v10.11.1 // indirect 116 | github.com/gobwas/glob v0.2.3 // indirect 117 | github.com/gogo/protobuf v1.3.2 // indirect 118 | github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 119 | github.com/golang/glog v1.0.0 // indirect 120 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 121 | github.com/golang/protobuf v1.5.2 // indirect 122 | github.com/golang/snappy v0.0.4 // indirect 123 | github.com/google/certificate-transparency-go v1.1.4 // indirect 124 | github.com/google/gnostic v0.6.9 // indirect 125 | github.com/google/go-cmp v0.5.9 // indirect 126 | github.com/google/go-containerregistry v0.12.0 // indirect 127 | github.com/google/go-github/v45 v45.2.0 // indirect 128 | github.com/google/go-querystring v1.1.0 // indirect 129 | github.com/google/gofuzz v1.2.0 // indirect 130 | github.com/google/trillian v1.5.1-0.20220819043421-0a389c4bb8d9 // indirect 131 | github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect 132 | github.com/googleapis/gax-go/v2 v2.7.0 // indirect 133 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 134 | github.com/hashicorp/go-retryablehttp v0.7.1 // indirect 135 | github.com/hashicorp/hcl v1.0.0 // indirect 136 | github.com/imdario/mergo v0.3.13 // indirect 137 | github.com/in-toto/in-toto-golang v0.5.0 // indirect 138 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 139 | github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect 140 | github.com/jmespath/go-jmespath v0.4.0 // indirect 141 | github.com/josharian/intern v1.0.0 // indirect 142 | github.com/json-iterator/go v1.1.12 // indirect 143 | github.com/klauspost/compress v1.15.12 // indirect 144 | github.com/leodido/go-urn v1.2.1 // indirect 145 | github.com/letsencrypt/boulder v0.0.0-20221028154552-0a02cdf7e37e // indirect 146 | github.com/magiconair/properties v1.8.6 // indirect 147 | github.com/mailru/easyjson v0.7.7 // indirect 148 | github.com/manifoldco/promptui v0.9.0 // indirect 149 | github.com/mattn/go-runewidth v0.0.14 // indirect 150 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 151 | github.com/miekg/pkcs11 v1.1.1 // indirect 152 | github.com/mitchellh/go-homedir v1.1.0 // indirect 153 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 154 | github.com/mitchellh/mapstructure v1.5.0 // indirect 155 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 156 | github.com/modern-go/reflect2 v1.0.2 // indirect 157 | github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect 158 | github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect 159 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 160 | github.com/oklog/ulid v1.3.1 // indirect 161 | github.com/open-policy-agent/opa v0.45.0 // indirect 162 | github.com/opencontainers/go-digest v1.0.0 // indirect 163 | github.com/opencontainers/image-spec v1.1.0-rc2 // indirect 164 | github.com/opentracing/opentracing-go v1.2.0 // indirect 165 | github.com/pelletier/go-toml v1.9.5 // indirect 166 | github.com/pelletier/go-toml/v2 v2.0.5 // indirect 167 | github.com/pkg/errors v0.9.1 // indirect 168 | github.com/protocolbuffers/txtpbfmt v0.0.0-20220926135727-61ed6f8e4d6e // indirect 169 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 170 | github.com/rivo/uniseg v0.4.2 // indirect 171 | github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect 172 | github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect 173 | github.com/segmentio/ksuid v1.0.4 // indirect 174 | github.com/shibumi/go-pathspec v1.3.0 // indirect 175 | github.com/sigstore/fulcio v1.0.0 // indirect 176 | github.com/sigstore/rekor v1.0.0 // indirect 177 | github.com/sigstore/sigstore v1.4.5 // indirect 178 | github.com/sirupsen/logrus v1.9.0 // indirect 179 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect 180 | github.com/spf13/afero v1.9.2 // indirect 181 | github.com/spf13/cast v1.5.0 // indirect 182 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 183 | github.com/spf13/pflag v1.0.5 // indirect 184 | github.com/spiffe/go-spiffe/v2 v2.1.1 // indirect 185 | github.com/subosito/gotenv v1.4.1 // indirect 186 | github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect 187 | github.com/tchap/go-patricia/v2 v2.3.1 // indirect 188 | github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect 189 | github.com/thales-e-security/pool v0.0.2 // indirect 190 | github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect 191 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect 192 | github.com/tjfoc/gmsm v1.4.1 // indirect 193 | github.com/transparency-dev/merkle v0.0.1 // indirect 194 | github.com/vbatts/tar-split v0.11.2 // indirect 195 | github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect 196 | github.com/xanzy/go-gitlab v0.74.0 // indirect 197 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 198 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 199 | github.com/yashtewari/glob-intersection v0.1.0 // indirect 200 | github.com/zeebo/errs v1.3.0 // indirect 201 | go.mongodb.org/mongo-driver v1.10.3 // indirect 202 | go.opencensus.io v0.23.0 // indirect 203 | go.uber.org/atomic v1.10.0 // indirect 204 | go.uber.org/multierr v1.8.0 // indirect 205 | go.uber.org/zap v1.23.0 // indirect 206 | golang.org/x/crypto v0.1.0 // indirect 207 | golang.org/x/exp v0.0.0-20221028150844-83b7d23a625f // indirect 208 | golang.org/x/mod v0.6.0 // indirect 209 | golang.org/x/net v0.1.0 // indirect 210 | golang.org/x/oauth2 v0.1.0 // indirect 211 | golang.org/x/sync v0.1.0 // indirect 212 | golang.org/x/sys v0.1.0 // indirect 213 | golang.org/x/term v0.1.0 // indirect 214 | golang.org/x/text v0.4.0 // indirect 215 | golang.org/x/time v0.1.0 // indirect 216 | golang.org/x/tools v0.2.0 // indirect 217 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 218 | google.golang.org/api v0.102.0 // indirect 219 | google.golang.org/appengine v1.6.7 // indirect 220 | google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66 // indirect 221 | google.golang.org/grpc v1.50.1 // indirect 222 | google.golang.org/protobuf v1.28.1 // indirect 223 | gopkg.in/inf.v0 v0.9.1 // indirect 224 | gopkg.in/ini.v1 v1.67.0 // indirect 225 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect 226 | gopkg.in/yaml.v2 v2.4.0 // indirect 227 | k8s.io/api v0.25.3 // indirect 228 | k8s.io/apimachinery v0.25.3 // indirect 229 | k8s.io/client-go v0.25.3 // indirect 230 | k8s.io/klog/v2 v2.80.1 // indirect 231 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect 232 | k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect 233 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 234 | sigs.k8s.io/release-utils v0.7.3 // indirect 235 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 236 | sigs.k8s.io/yaml v1.3.0 // indirect 237 | ) 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://user-images.githubusercontent.com/109651023/189649537-95638785-618f-4c74-93af-2cafedec2f07.png) 2 | 3 | ## Public Archival 4 | 5 | This project has been publicly archived. Please visit **[github.com/openclarity](https://github.com/openclarity)** or **[openclarity.io](https://openclarity.io)** to discover our other projects! 6 | 7 | ## Description 8 | 9 | FunctionClarity is a code integrity solution for serverless functions. It allows users to sign their serverless functions and verify their integrity prior to their execution in their cloud environments. FunctionClarity includes a CLI tool, complemented by a "verification" function deployed in the target cloud account. The solution is designed for CI/CD insertion, where the serverless function code/images can be signed and uploaded before the function is created in the cloud repository. 10 | 11 | This version supports serverless functions on AWS (Lambda functions) only, support for Azure functions and Google functions/cloud-run are part of the near-term roadmap plan. 12 | 13 | ## How does it work? 14 | 15 | 16 | ![FunctionClarity Diagram](https://user-images.githubusercontent.com/110329574/197707276-10cd650e-a84d-4a9f-a83a-ee60dc2cbf28.png) 17 | 18 | * Deploy FunctionClarity – deploy FunctionClarity "validation" function in the target cloud account (a one time operation); this function will scan and verify new functions when created or updated in the target account 19 | * Sign functions - use FunctionClarity CLI to sign the function code or image in the user’s environment, and then upload it to the target cloud account 20 | * Deploy the serverless function - using the signed function code/image 21 | * Verify functions - the FunctionClarity verifier function is triggered when user functions are created or updated in case they meet the filter criteria, and does the following: 22 | * Fetches the function code from the cloud account 23 | * Verifies the signature of the function code image or zip file 24 | * Follows one of these actions, based on the verification results: 25 | * Detect - marks the function with the verification results 26 | * Block - tags the function as 'blocked', if the signature is not correctly verified, otherwise does nothing 27 | * Notify - sends a notification of the verification results to an SNS queue 28 | 29 | If a function is tagged as blocked, it will be prevented from being run by AWS when it is invoked. 30 | 31 | ## Download FunctionClarity 32 | Go to the [function clarity latest release](https://github.com/openclarity/functionclarity/releases/latest): 33 | * Create a folder and download: 34 | * ```aws_function.tar.gz``` and extract it to the folder 35 | * ```functionclarity-v``` for your OS type and extract it to the folder 36 | 37 | ## Quick start 38 | This section explains how to get started using FunctionClarity. These steps are involved: 39 | 40 | * Initialize and deploy FunctionClarity 41 | * Sign and upload serverless function code 42 | * Sign and verify new AWS functions 43 | * Verify functions: 44 | * From the cloud account 45 | * From the FunctionClarity command line 46 | 47 | ### Initialize and deploy FunctionClarity 48 | Follow these steps from a command line, to install FunctionClarity in your AWS account. 49 | Run the command from the folder in which the FunctionClarity tar file is located. 50 | As part of the deployment, a verifier function will be deployed in your cloud account, which will be triggered when lambda functions are created or updated in the account. This function verifies function identities and signatures, according to the FunctionClarity settings. 51 | A configuration file will also be created locally, in ```~/.fc```, with default values that are used when signing or verifying functions, unless specific settings are set with command line flags. 52 | 53 | 1. Run the command ```./functionclarity init aws``` 54 | 2. When prompted, enter the following details: 55 | ``` 56 | enter Access Key: ******** 57 | enter Secret Key: ******** 58 | enter region: 59 | enter default bucket (you can leave empty and a bucket with name functionclarity will be created): 60 | enter tag keys of functions to include in the verification (leave empty to include all): 61 | enter the function regions to include in the verification, i.e: us-east-1,us-west-1 (leave empty to include all): 62 | select post verification action : (1) for detect; (2) for block; leave empty for no post verification action to perform 63 | enter SNS arn if you would like to be notified when signature verification fails, otherwise press enter: 64 | is there existing trail in CloudTrail (in the region selected above) which you would like to use? (if no, please press enter): 65 | do you want to work in keyless mode (y/n): n 66 | enter path to custom public key for code signing? (if you want us to generate key pair, please press enter): 67 | Enter password for private key: 68 | Enter password for private key again: 69 | Private key written to cosign.key 70 | Public key written to cosign.pub 71 | Uploading function-clarity function code to s3 bucket, this may take a few minutes 72 | function-clarity function code upload successfully 73 | deployment request sent to provider 74 | waiting for deployment to complete 75 | deployment finished successfully 76 | ``` 77 | 78 | ### Sign function code 79 | Use the command below to sign a folder containing function code, and then upload it to the user cloud account. 80 | 81 | ```shell 82 | ./functionclarity sign aws code /sample-code-verified-folder 83 | 84 | using config file: /Users/john/.fc 85 | Enter password for private key: 86 | 87 | Code uploaded successfully 88 | ``` 89 | ### Deploy a function or update function code 90 | Use AWS cli to deploy a signed lambda function to your cloud account, or to update lambda code in the account 91 | 92 | ### Verify function code 93 | 94 | #### Verify automatically on function create or update events 95 | 96 | If the verifier function is deployed in your account, and in case it meets the filter criteria then any function create or update event will trigger it to verify the new or updated function. It will follow the post-verification action (detect, block, or notify). 97 | 98 | If the action is 'detect', the function will be tagged with the FunctionClarity message that the function is verified: 99 | 100 | ![image](https://user-images.githubusercontent.com/109651023/189880644-bed91413-a81c-4b03-b6f8-00ebea6606a0.png) 101 | 102 | If the action is block, the function's concurrency will be set to 0 and the function will be throttled: 103 | 104 | ![image](https://user-images.githubusercontent.com/109651023/201917880-d2d2e1c4-dec7-4930-8930-0b8dc655cb0b.png) 105 | 106 | 107 | #### Verify manually 108 | You can also use the CLI to manually verify a function. In this case, the function is downloaded from the cloud account, and then verified locally. 109 | 110 | ```shell 111 | ./functionclarity verify aws funcclarity-test-signed --function-region=us-east-2 112 | using config file: /Users/john/.fc 113 | Verified OK 114 | ``` 115 | 116 | ## Advanced use 117 | FunctionClarity includes several advanced commands and features, which are described below. 118 | 119 | FunctionClarity leverages [cosign](https://github.com/sigstore/cosign) to sign and verify, code, for both key-pair and keyless signing techniques. 120 | 121 | ### Init command detailed use 122 | ```shell 123 | ./functionclarity init aws 124 | ``` 125 | | Argument | Description | 126 | |-----------------------------|----------------------------------------------------------------------------------------------------| 127 | | access key | AWS access key | 128 | | secret key | AWS secret key | 129 | | region | AWS region in which to deploy FunctionClarity | 130 | | default bucket | AWS bucket in which to deploy code signatures and FunctionClarity verifier lambda code for the deployment | 131 | | post verification action | action to perform after verification (detect, block; leave empty for no action to be performed) | 132 | | sns arn | an SNS queue for notifications if verification fails, leave empty to skip notifications | 133 | | CloudTrail | AWS cloudtrail to use; if empty a new trail will be created | 134 | | keyless mode (y/n) | work in keyless mode | 135 | | public key for code signing | path to public key to use when verifying functions; if blank a new key-pair will be created | 136 | | privte key for code signing | private key path; used only if a public key path is also supplied | 137 | | function tag keys to include| tag keys of functions to include in the verification; if empty all functions will be included | 138 | | function regions to include | function regions to include in the verification, i.e: us-east-1,us-west-1; if empty functions from all regions will be included | 139 | 140 | | Flag | Description | 141 | |--------------------|-------------------------------------------------------------------------| 142 | | only-create-config | determine whether to only create config file without actually deploying | 143 | 144 | ### Import your own signing key 145 | The ```import-key-pair``` command provide the ability to import your existing PEM-encoded, RSA or EC private key, use this command: 146 | ```shell 147 | ./function-clarity import-key-pair --key key.pem 148 | ``` 149 | 150 | ### Deploy command detailed use 151 | The ```deploy``` command does the same as ```init```, but it uses the config file, so you don't 152 | need to supply parameters using the command line 153 | ```shell 154 | ./functionclarity deploy aws 155 | ``` 156 | 157 | ### Sign command detailed use 158 | FunctionClarity supports signing code from local folders and images. 159 | When signing images, you must be logged in to the docker repository where your images deployed. 160 | 161 | 162 | --- 163 | 164 | **NOTE**: 165 | If a default config file exists (in ```~/.fc```) it will be used. If a custom config file flag is included in the command line, it will be used instead of the default file. If flags are included in the command line, they will be used and take precedence. 166 | 167 | --- 168 | ### Examples 169 | To sign code, use this command: 170 | ```shell 171 | ./functionclarity sign aws code --flags (optional if you have configuration file) 172 | ``` 173 | To sign images, use this command: 174 | ```shell 175 | ./functionclarity sign aws image --flags (optional if you have configuration file) 176 | ``` 177 | These are optional flags for the ```sign``` command: 178 | 179 | | flag | Description | 180 | |------------|------------------------------------------------------------------| 181 | | access key | AWS access key | 182 | | secret key | AWS secret key | 183 | | region | AWS region in which to deploy signature (relevant only for code signing) | 184 | | bucket | AWS bucket in which to deploy code signature (relevant only for code signing) | 185 | | privatekey | key to use to sign code | 186 | 187 | 188 | ### Verify command detailed use 189 | 190 | --- 191 | 192 | **NOTE**: 193 | If a default config file exists (in ```~/.fc```) it will be used. If a custom config file flag is included in the command line, it will be used instead of the default file. If flags are included in the command line, they will be used and take precedence. 194 | 195 | --- 196 | 197 | Command for verification 198 | ```shell 199 | ./functionclarity verify aws --function-region= --flags (optional if you have configuration file) 200 | ``` 201 | 202 | These are optional flags for the ```verify``` command: 203 | 204 | | flag | Description | 205 | |------------|--------------------------------------------------------------------| 206 | | access key | AWS access key | 207 | | secret key | AWS secret key | 208 | | region | AWS region from which to load the signature from (relevant only for code signing) | 209 | | bucket | AWS bucket from which to load signatures from (relevant only for code signing) | 210 | | key | public key for verification | 211 | 212 | ### Update verifier function configuration command detailed use 213 | 214 | The ```update-func-config``` command is usefull when you want to update configuration related to the verifier lambda. The command updates the runtime configuration of the verifier lambda function in the aws environment. 215 | 216 | --- 217 | 218 | **NOTE**: 219 | If a default config file exists (in ```~/.fc```) it will be used. If a custom config file flag is included in the command line, it will be used instead of the default file. If flags are included in the command line, they will be used and take precedence. 220 | 221 | --- 222 | 223 | Command for updating verifier configuration 224 | ```shell 225 | ./function-clarity update-func-config aws --flags (optional if you have configuration file) 226 | ``` 227 | 228 | These are optional flags for the ```update-func-config``` command: 229 | 230 | | flag | Description | 231 | |------------|--------------------------------------------------------------------| 232 | | access key | AWS access key | 233 | | secret key | AWS secret key | 234 | | region | AWS region where the verifier lambda runs | 235 | | action | action to perform after verification (detect, block; leave empty for no action to be performed) | 236 | | includedfunctagkeys | tag keys of functions to include in the verification; if empty all functions will be included | 237 | | includedfuncregions | function regions to include in the verification, i.e: us-east-1,us-west-1; if empty functions from all regions will be included| 238 | | snsTopicArn | an SNS queue for notifications if verification fails, leave empty to skip notifications | 239 | 240 | -------------------------------------------------------------------------------- /test/e2e_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Cisco Systems, Inc. and its affiliates. 2 | // All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package test 17 | 18 | import ( 19 | "archive/zip" 20 | "context" 21 | "encoding/base64" 22 | "errors" 23 | "fmt" 24 | "io" 25 | "log" 26 | "os" 27 | "strconv" 28 | "strings" 29 | "testing" 30 | "time" 31 | 32 | "github.com/aws/aws-sdk-go-v2/aws" 33 | "github.com/aws/aws-sdk-go-v2/config" 34 | "github.com/aws/aws-sdk-go-v2/credentials" 35 | "github.com/aws/aws-sdk-go-v2/service/cloudformation" 36 | "github.com/aws/aws-sdk-go-v2/service/ecr" 37 | et "github.com/aws/aws-sdk-go-v2/service/ecr/types" 38 | "github.com/aws/aws-sdk-go-v2/service/lambda" 39 | "github.com/aws/aws-sdk-go-v2/service/lambda/types" 40 | "github.com/aws/aws-sdk-go-v2/service/s3" 41 | "github.com/aws/aws-sdk-go-v2/service/sqs" 42 | sqsTypes "github.com/aws/aws-sdk-go-v2/service/sqs/types" 43 | "github.com/aws/smithy-go" 44 | "github.com/openclarity/functionclarity/pkg/clients" 45 | i "github.com/openclarity/functionclarity/pkg/init" 46 | "github.com/openclarity/functionclarity/pkg/integrity" 47 | o "github.com/openclarity/functionclarity/pkg/options" 48 | "github.com/openclarity/functionclarity/pkg/sign" 49 | "github.com/openclarity/functionclarity/pkg/utils" 50 | "github.com/sigstore/cosign/cmd/cosign/cli/generate" 51 | "github.com/sigstore/cosign/cmd/cosign/cli/options" 52 | s "github.com/sigstore/cosign/cmd/cosign/cli/sign" 53 | "github.com/spf13/viper" 54 | "gopkg.in/yaml.v3" 55 | ) 56 | 57 | const ( 58 | zipName = "test-function.zip" 59 | codeFuncNameSigned = "e2eTestCodeSigned" 60 | codeFuncNameNotSigned = "e2eTestCodeNotSigned" 61 | imageFuncName = "e2eTestImage" 62 | role = "arn:aws:iam::813189926740:role/e2eTest" 63 | repoName = "helloworld" 64 | imageUri = "813189926740.dkr.ecr.us-east-1.amazonaws.com/helloworld:v1" 65 | publicKey = "cosign.pub" 66 | privateKey = "cosign.key" 67 | pass = "pass" 68 | verifierFunctionName = "FunctionClarityLambda" 69 | ) 70 | 71 | var awsClient *clients.AwsClient 72 | var lambdaClient *lambda.Client 73 | var formationClient *cloudformation.Client 74 | var sqsClient *sqs.Client 75 | var s3Client *s3.Client 76 | var ecrClient *ecr.Client 77 | 78 | var keyPass = []byte(pass) 79 | var suffix string 80 | 81 | var passFunc = func(_ bool) ([]byte, error) { 82 | return keyPass, nil 83 | } 84 | 85 | var accessKey, secretKey, bucket, region, lambdaRegion string 86 | 87 | var ro = &options.RootOptions{Timeout: options.DefaultTimeout} 88 | 89 | const includeFuncTag = "funcclarity-e2e-tag" 90 | 91 | func TestMain(m *testing.M) { 92 | setup() 93 | code := m.Run() 94 | parseBool, err := strconv.ParseBool(os.Getenv("is_start")) 95 | if err != nil { 96 | panic("is_start not bool") 97 | } 98 | if !parseBool { 99 | shutdown() 100 | } 101 | os.Exit(code) 102 | } 103 | 104 | func setup() { 105 | if err := os.MkdirAll(utils.FunctionClarityHomeDir, os.ModePerm); err != nil { 106 | log.Fatal(err) 107 | } 108 | suffix = getEnvVar("uuid", "test uuid") 109 | fmt.Printf("uuid: %s\n", suffix) 110 | accessKey = getEnvVar("ACCESS_KEY", "access key") 111 | secretKey = getEnvVar("SECRET_KEY", "secret key") 112 | bucket = getEnvVar("BUCKET", "bucket") + suffix 113 | region = getEnvVar("REGION", "region") 114 | lambdaRegion = getEnvVar("FUNCTION_REGION", "function region") 115 | 116 | awsClient = clients.NewAwsClient(accessKey, secretKey, bucket, region, lambdaRegion) 117 | 118 | cfg := createConfig(region) 119 | lambdaClient = lambda.NewFromConfig(*createConfig(lambdaRegion)) 120 | formationClient = cloudformation.NewFromConfig(*cfg) 121 | sqsClient = sqs.NewFromConfig(*cfg) 122 | s3Client = s3.NewFromConfig(*cfg) 123 | ecrClient = ecr.NewFromConfig(*cfg) 124 | 125 | if err := integrity.InitDocker(awsClient); err != nil { 126 | log.Fatal(err) 127 | } 128 | 129 | parseBool, err := strconv.ParseBool(os.Getenv("is_start")) 130 | if err != nil { 131 | panic("is_start not bool") 132 | } 133 | if parseBool { 134 | var configForDeployment i.AWSInput 135 | configForDeployment.Bucket = bucket 136 | configForDeployment.Action = "block" 137 | configForDeployment.Region = region 138 | configForDeployment.IsKeyless = false 139 | configForDeployment.SnsTopicArn = "arn:aws:sns:us-east-1:813189926740:func-clarity-e2e" 140 | configForDeployment.IncludedFuncTagKeys = []string{includeFuncTag + suffix} 141 | if err := awsClient.DeployFunctionClarity("SecurecnMonitoringTrail", publicKey, configForDeployment, suffix); err != nil { 142 | log.Fatal(err) 143 | } 144 | time.Sleep(2 * time.Minute) 145 | } 146 | } 147 | 148 | func shutdown() { 149 | deleteS3TrailBucketContent() 150 | deleteStack() 151 | deleteS3Bucket(bucket) 152 | deleteLambda(codeFuncNameSigned + suffix) 153 | deleteLambda(codeFuncNameNotSigned + suffix) 154 | deleteLambda(imageFuncName + suffix) 155 | } 156 | 157 | func TestCodeNotSignedAndVerify(t *testing.T) { 158 | viper.Set("privatekey", privateKey) 159 | switchConfiguration(false, publicKey) 160 | functionArn := initCodeLambda(t, codeFuncNameNotSigned) 161 | success, timeout := findTag(t, functionArn, lambdaClient, utils.FunctionVerifyResultTagKey, utils.FunctionNotSignedTagValue) 162 | if timeout { 163 | t.Fatal("test failed on timout, the required tag not added in the time period") 164 | } 165 | if !success { 166 | t.Fatal("test failure: no " + utils.FunctionNotSignedTagValue + " tag in the signed function") 167 | } 168 | fmt.Println(utils.FunctionNotSignedTagValue + " tag found in the signed function") 169 | concurrencyLevel, err := awsClient.GetConcurrencyLevel(functionArn) 170 | if err != nil { 171 | t.Fatal("failed to get functions concurrency level") 172 | } 173 | if concurrencyLevel == nil { 174 | t.Fatal("concurrency level not set to 0") 175 | } 176 | if *concurrencyLevel != 0 { 177 | t.Fatal("Function not blocked") 178 | } 179 | queueInput := &sqs.GetQueueUrlInput{ 180 | QueueName: aws.String("func-clarity-e2e"), 181 | } 182 | GetQueueOutput, err := sqsClient.GetQueueUrl(context.TODO(), queueInput) 183 | if err != nil { 184 | t.Fatal("Failed to get sqs details", err) 185 | } 186 | queueUrl := GetQueueOutput.QueueUrl 187 | GetMessagesInput := &sqs.ReceiveMessageInput{ 188 | MessageAttributeNames: []string{ 189 | string(sqsTypes.QueueAttributeNameAll), 190 | }, 191 | QueueUrl: queueUrl, 192 | MaxNumberOfMessages: 10, 193 | VisibilityTimeout: int32(1), 194 | } 195 | receiveMessageOutput, err := sqsClient.ReceiveMessage(context.TODO(), GetMessagesInput) 196 | if err != nil { 197 | t.Fatal("Failed to get sqs messages") 198 | } 199 | foundMessage := false 200 | if receiveMessageOutput.Messages != nil { 201 | for _, message := range receiveMessageOutput.Messages { 202 | if strings.Contains(*message.Body, codeFuncNameNotSigned+suffix) { 203 | foundMessage = true 204 | } 205 | } 206 | } else { 207 | t.Fatal("No messages found in queue") 208 | } 209 | if !foundMessage { 210 | t.Fatal("Message doesn't contain func name.") 211 | } 212 | deleteLambda(codeFuncNameNotSigned + suffix) 213 | } 214 | 215 | func TestCodeSignAndVerify(t *testing.T) { 216 | viper.Set("privatekey", privateKey) 217 | switchConfiguration(false, publicKey) 218 | 219 | funcDefer, err := mockStdin(t, pass) 220 | if err != nil { 221 | t.Fatal(err) 222 | } 223 | defer funcDefer() 224 | 225 | sbo := o.SignBlobOptions{ 226 | SignBlobOptions: options.SignBlobOptions{ 227 | Base64Output: true, 228 | Registry: options.RegistryOptions{}, 229 | }, 230 | } 231 | err = sign.SignAndUploadCode(awsClient, "utils/testing_lambda", &sbo, ro) 232 | if err != nil { 233 | t.Fatal(err) 234 | } 235 | 236 | functionArn := initCodeLambda(t, codeFuncNameSigned) 237 | 238 | successTagValue := utils.FunctionSignedTagValue 239 | success, timeout := findTag(t, functionArn, lambdaClient, utils.FunctionVerifyResultTagKey, successTagValue) 240 | if timeout { 241 | t.Fatal("test failed on timout, the required tag not added in the time period") 242 | } 243 | if !success { 244 | t.Fatal("test failure: no " + successTagValue + " tag in the signed function") 245 | } 246 | fmt.Println(successTagValue + " tag found in the signed function") 247 | deleteLambda(codeFuncNameSigned + suffix) 248 | deleteS3BucketContent(&bucket, []string{"function-clarity.zip"}) 249 | } 250 | 251 | func TestImageSignAndVerify(t *testing.T) { 252 | switchConfiguration(false, publicKey) 253 | 254 | funcDefer, err := mockStdin(t, pass) 255 | if err != nil { 256 | t.Fatal(err) 257 | } 258 | defer funcDefer() 259 | 260 | ko := options.KeyOpts{KeyRef: privateKey, PassFunc: passFunc} 261 | err = s.SignCmd(ro, ko, options.RegistryOptions{}, nil, []string{imageUri}, "", "", true, "", "", "", false, false, "", false) 262 | if err != nil { 263 | t.Fatal(err) 264 | } 265 | 266 | functionArn, err := createImageLambda(t) 267 | if err != nil { 268 | t.Fatal(err) 269 | } 270 | 271 | successTagValue := "Function signed and verified" 272 | success, timeout := findTag(t, functionArn, lambdaClient, "Function clarity result", successTagValue) 273 | if timeout { 274 | t.Fatal("test failed on timout, the required tag not added in the time period") 275 | } 276 | if !success { 277 | t.Fatal("test failure: no " + successTagValue + " tag in the signed function") 278 | } 279 | fmt.Println(successTagValue + " tag found in the signed function") 280 | deleteLambda(imageFuncName + suffix) 281 | cleanupImages() 282 | } 283 | 284 | func TestCodeImageAndVerifyKeyless(t *testing.T) { 285 | switchConfiguration(true, "") 286 | jwt := getEnvVar("jwt_token", "token ID") 287 | 288 | ko := options.KeyOpts{ 289 | FulcioURL: options.DefaultFulcioURL, 290 | IDToken: jwt, 291 | RekorURL: options.DefaultRekorURL, 292 | PassFunc: generate.GetPass, 293 | KeyRef: "", 294 | Sk: false, 295 | Slot: "", 296 | InsecureSkipFulcioVerify: false, 297 | OIDCIssuer: "https://oauth2.sigstore.dev/auth", 298 | OIDCClientID: "sigstore", 299 | OIDCClientSecret: "", 300 | OIDCRedirectURL: "", 301 | OIDCDisableProviders: false, 302 | OIDCProvider: "", 303 | SkipConfirmation: false, 304 | } 305 | err := s.SignCmd(ro, ko, options.RegistryOptions{}, nil, []string{imageUri}, "", "", true, "", "", "", true, false, "", false) 306 | if err != nil { 307 | t.Fatal(err) 308 | } 309 | 310 | functionArn, err := createImageLambda(t) 311 | if err != nil { 312 | t.Fatal(err) 313 | } 314 | 315 | successTagValue := "Function signed and verified" 316 | success, timeout := findTag(t, functionArn, lambdaClient, "Function clarity result", successTagValue) 317 | if timeout { 318 | t.Fatal("test failed on timout, the required tag not added in the time period") 319 | } 320 | if !success { 321 | t.Fatal("test failure: no " + successTagValue + " tag in the signed function") 322 | } 323 | fmt.Println(successTagValue + " tag found in the signed function") 324 | deleteLambda(imageFuncName + suffix) 325 | cleanupImages() 326 | } 327 | 328 | func TestCodeSignAndVerifyKeyless(t *testing.T) { 329 | switchConfiguration(true, "") 330 | 331 | jwt := getEnvVar("jwt_token", "token ID") 332 | sbo := o.SignBlobOptions{ 333 | SignBlobOptions: options.SignBlobOptions{ 334 | Base64Output: true, 335 | Registry: options.RegistryOptions{}, 336 | SkipConfirmation: true, 337 | Fulcio: options.FulcioOptions{URL: options.DefaultFulcioURL, IdentityToken: jwt}, 338 | Rekor: options.RekorOptions{URL: options.DefaultRekorURL}, 339 | }, 340 | } 341 | 342 | err := sign.SignAndUploadCode(awsClient, "utils/testing_lambda", &sbo, ro) 343 | if err != nil { 344 | t.Fatal(err) 345 | } 346 | 347 | functionArn := initCodeLambda(t, codeFuncNameSigned) 348 | 349 | successTagValue := "Function signed and verified" 350 | success, timeout := findTag(t, functionArn, lambdaClient, "Function clarity result", successTagValue) 351 | if timeout { 352 | t.Fatal("test failed on timout, the required tag not added in the time period") 353 | } 354 | if !success { 355 | t.Fatal("test failure: no " + successTagValue + " tag in the signed function") 356 | } 357 | fmt.Println(successTagValue + " tag found in the signed function") 358 | deleteLambda(codeFuncNameSigned + suffix) 359 | deleteS3BucketContent(&bucket, []string{"function-clarity.zip"}) 360 | } 361 | 362 | func findTag(t *testing.T, functionArn string, lambdaClient *lambda.Client, successTagKey string, successTagValue string) (bool, bool) { 363 | t.Helper() 364 | var timeout bool 365 | timer := time.NewTimer(10 * time.Minute) 366 | go func() { 367 | <-timer.C 368 | timeout = true 369 | }() 370 | defer func() { 371 | timer.Stop() 372 | }() 373 | 374 | var result *lambda.ListTagsOutput 375 | var err error 376 | for { 377 | result, err = lambdaClient.ListTags(context.TODO(), &lambda.ListTagsInput{Resource: &functionArn}) 378 | if err != nil { 379 | t.Fatal("failed to get functions tags") 380 | } 381 | for key, value := range result.Tags { 382 | if key == successTagKey && value == successTagValue { 383 | return true, false 384 | } else { 385 | time.Sleep(10 * time.Second) 386 | } 387 | } 388 | if timeout { 389 | return false, true 390 | } 391 | } 392 | } 393 | 394 | func switchConfiguration(isKeyless bool, publicKey string) { 395 | funcCfg, err := lambdaClient.GetFunctionConfiguration(context.TODO(), &lambda.GetFunctionConfigurationInput{FunctionName: aws.String(verifierFunctionName + suffix)}) 396 | if err != nil { 397 | log.Fatal("failed to get function configuration") 398 | } 399 | cfg := funcCfg.Environment.Variables["CONFIGURATION"] 400 | decodedConfig, err := base64.StdEncoding.DecodeString(cfg) 401 | if err != nil { 402 | log.Fatal("failed to decode config from base64") 403 | } 404 | var config *i.AWSInput = nil 405 | err = yaml.Unmarshal(decodedConfig, &config) 406 | if err != nil { 407 | log.Fatal("failed to unmarshal config from yaml") 408 | } 409 | config.IsKeyless = isKeyless 410 | config.PublicKey = publicKey 411 | 412 | cfgYaml, err := yaml.Marshal(&config) 413 | if err != nil { 414 | log.Fatal("failed to marshal config to yaml") 415 | } 416 | encodedConfig := base64.StdEncoding.EncodeToString(cfgYaml) 417 | funcCfg.Environment.Variables["CONFIGURATION"] = encodedConfig 418 | params := &lambda.UpdateFunctionConfigurationInput{ 419 | FunctionName: funcCfg.FunctionName, 420 | Environment: &types.Environment{Variables: funcCfg.Environment.Variables}, 421 | } 422 | _, err = lambdaClient.UpdateFunctionConfiguration(context.TODO(), params) 423 | if err != nil { 424 | log.Fatalf("failed to update function configuration: %v", err) 425 | } 426 | time.Sleep(30 * time.Second) 427 | } 428 | 429 | func createConfig(region string) *aws.Config { 430 | cfg, err := config.LoadDefaultConfig(context.TODO(), 431 | config.WithRegion(region), 432 | config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, ""))) 433 | if err != nil { 434 | panic(fmt.Sprintf("failed loading config: %v", err)) 435 | } 436 | return &cfg 437 | } 438 | 439 | func getEnvVar(key string, name string) string { 440 | v, b := os.LookupEnv(key) 441 | if !b { 442 | log.Fatal(name + " not found in the environment") 443 | } 444 | return v 445 | } 446 | 447 | func deleteS3TrailBucketContent() { 448 | result, err := s3Client.ListBuckets(context.TODO(), &s3.ListBucketsInput{}) 449 | if err != nil { 450 | fmt.Println("Got an error retrieving buckets:") 451 | fmt.Println(err) 452 | return 453 | } 454 | for _, bucket := range result.Buckets { 455 | if strings.HasPrefix(*bucket.Name, "function-clarity-stack") { 456 | bl, err := s3Client.GetBucketLocation(context.TODO(), &s3.GetBucketLocationInput{Bucket: bucket.Name}) 457 | if err != nil { 458 | continue 459 | } 460 | if string(bl.LocationConstraint) == region { 461 | deleteS3BucketContent(bucket.Name, []string{}) 462 | } 463 | } 464 | } 465 | } 466 | 467 | func deleteS3BucketContent(name *string, except []string) { 468 | listObjectsV2Response, err := s3Client.ListObjectsV2(context.TODO(), 469 | &s3.ListObjectsV2Input{ 470 | Bucket: name, 471 | }) 472 | 473 | for { 474 | 475 | if err != nil { 476 | log.Fatalf("Couldn't list objects... delete all objects in bucket: %s failed", *name) 477 | } 478 | for _, item := range listObjectsV2Response.Contents { 479 | if !contains(except, *item.Key) { 480 | _, err = s3Client.DeleteObject(context.Background(), &s3.DeleteObjectInput{ 481 | Bucket: name, 482 | Key: item.Key, 483 | }) 484 | 485 | if err != nil { 486 | log.Fatalf("delete all objects in bucket: %s failed", *name) 487 | } 488 | } 489 | } 490 | 491 | if listObjectsV2Response.IsTruncated { 492 | listObjectsV2Response, err = s3Client.ListObjectsV2(context.TODO(), 493 | &s3.ListObjectsV2Input{ 494 | Bucket: name, 495 | ContinuationToken: listObjectsV2Response.ContinuationToken, 496 | }) 497 | } else { 498 | break 499 | } 500 | } 501 | } 502 | 503 | func contains(s []string, str string) bool { 504 | for _, v := range s { 505 | if v == str { 506 | return true 507 | } 508 | } 509 | 510 | return false 511 | } 512 | 513 | func deleteS3Bucket(name string) { 514 | if _, err := s3Client.HeadBucket(context.TODO(), &s3.HeadBucketInput{Bucket: aws.String(name)}); err != nil { 515 | return 516 | } 517 | deleteS3BucketContent(&name, []string{}) 518 | if _, err := s3Client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{Bucket: aws.String(name)}); err != nil { 519 | log.Fatalf("delete bucket: %s failed", name) 520 | } 521 | } 522 | 523 | func deleteStack() { 524 | stackName := "function-clarity-stack" + suffix 525 | _, err := formationClient.DeleteStack(context.TODO(), &cloudformation.DeleteStackInput{ 526 | StackName: &stackName, 527 | }) 528 | if err != nil { 529 | fmt.Println("Got an error deleting stack " + stackName) 530 | return 531 | } 532 | 533 | var timeout bool 534 | timer := time.NewTimer(5 * time.Minute) 535 | go func() { 536 | <-timer.C 537 | timeout = true 538 | }() 539 | defer func() { 540 | timer.Stop() 541 | }() 542 | 543 | for { 544 | var gae *smithy.GenericAPIError 545 | if _, err := formationClient.DescribeStacks(context.TODO(), &cloudformation.DescribeStacksInput{StackName: aws.String(stackName)}); err != nil { 546 | if errors.As(err, &gae) && gae.ErrorMessage() == "Stack with id function-clarity-stack"+suffix+"does not exist" { 547 | log.Println("Deleted stack " + stackName) 548 | return 549 | } 550 | log.Println("Got an error waiting for stack to be deleted", err) 551 | return 552 | } 553 | if timeout { 554 | log.Fatal("timout on waiting for stack to delete") 555 | } 556 | time.Sleep(15 * time.Second) 557 | } 558 | } 559 | 560 | func deleteLambda(name string) { 561 | deleteArgs := &lambda.DeleteFunctionInput{ 562 | FunctionName: &name, 563 | } 564 | lambdaClient.DeleteFunction(context.TODO(), deleteArgs) //nolint:errcheck 565 | } 566 | 567 | func cleanupImages() { 568 | images, err := ecrClient.ListImages(context.TODO(), &ecr.ListImagesInput{RepositoryName: aws.String(repoName)}) 569 | if err != nil { 570 | log.Fatal("Failed to get list of images", err) 571 | } 572 | var imageIds []et.ImageIdentifier 573 | for _, imageId := range images.ImageIds { 574 | if *imageId.ImageTag != "v1" && *imageId.ImageTag != "v2" { 575 | imageIds = append(imageIds, imageId) 576 | } 577 | } 578 | if _, err := ecrClient.BatchDeleteImage(context.TODO(), &ecr.BatchDeleteImageInput{RepositoryName: aws.String(repoName), ImageIds: imageIds}); err != nil { 579 | log.Fatal("Failed to delete images", err) 580 | } 581 | } 582 | 583 | func initCodeLambda(t *testing.T, funcName string) string { 584 | t.Helper() 585 | 586 | if err := createCodeZip(t); err != nil { 587 | t.Fatal(err) 588 | } 589 | functionArn, err := createCodeLambda(t, funcName) 590 | if err != nil { 591 | t.Fatal(err) 592 | } 593 | return functionArn 594 | } 595 | 596 | func createCodeLambda(t *testing.T, name string) (string, error) { 597 | t.Helper() 598 | 599 | contents, err := os.ReadFile(zipName) 600 | if err != nil { 601 | fmt.Println("Got error trying to read " + zipName) 602 | return "", err 603 | } 604 | 605 | handler := "testing_lambda" 606 | createArgs := &lambda.CreateFunctionInput{ 607 | Code: &types.FunctionCode{ZipFile: contents}, 608 | FunctionName: aws.String(name + suffix), 609 | Handler: aws.String(handler), 610 | Role: aws.String(role), 611 | Runtime: types.RuntimeGo1x, 612 | Tags: map[string]string{includeFuncTag + suffix: ""}, 613 | } 614 | result, err := lambdaClient.CreateFunction(context.TODO(), createArgs) 615 | if err != nil { 616 | fmt.Println("Cannot create function") 617 | return "", err 618 | } 619 | return *result.FunctionArn, nil 620 | } 621 | 622 | func createImageLambda(t *testing.T) (string, error) { 623 | t.Helper() 624 | createArgs := &lambda.CreateFunctionInput{ 625 | Code: &types.FunctionCode{ImageUri: aws.String(imageUri)}, 626 | FunctionName: aws.String(imageFuncName + suffix), 627 | Role: aws.String(role), 628 | PackageType: types.PackageTypeImage, 629 | Tags: map[string]string{includeFuncTag + suffix: ""}, 630 | } 631 | result, err := lambdaClient.CreateFunction(context.TODO(), createArgs) 632 | if err != nil { 633 | fmt.Println("Cannot create function") 634 | return "", err 635 | } 636 | return *result.FunctionArn, nil 637 | } 638 | 639 | func createCodeZip(t *testing.T) error { 640 | t.Helper() 641 | 642 | archive, err := os.Create(zipName) 643 | if err != nil { 644 | return err 645 | } 646 | defer archive.Close() 647 | zipWriter := zip.NewWriter(archive) 648 | binaryFile, err := os.Open("utils/testing_lambda") 649 | if err != nil { 650 | return err 651 | } 652 | defer binaryFile.Close() 653 | 654 | w1, err := zipWriter.Create("testing_lambda") 655 | if err != nil { 656 | return err 657 | } 658 | if _, err := io.Copy(w1, binaryFile); err != nil { 659 | return err 660 | } 661 | zipWriter.Close() 662 | return nil 663 | } 664 | 665 | func mockStdin(t *testing.T, dummyInput string) (funcDefer func(), err error) { 666 | t.Helper() 667 | 668 | tmpfile, err := os.CreateTemp(t.TempDir(), t.Name()) 669 | 670 | if err != nil { 671 | return nil, err 672 | } 673 | 674 | content := []byte(dummyInput) 675 | 676 | if _, err := tmpfile.Write(content); err != nil { 677 | return nil, err 678 | } 679 | 680 | if _, err := tmpfile.Seek(0, 0); err != nil { 681 | return nil, err 682 | } 683 | 684 | oldOsStdin := os.Stdin 685 | os.Stdin = tmpfile 686 | 687 | return func() { 688 | os.Stdin = oldOsStdin 689 | os.Remove(tmpfile.Name()) 690 | }, nil 691 | } 692 | --------------------------------------------------------------------------------