├── 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 | 
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 | 
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 | 
101 |
102 | If the action is block, the function's concurrency will be set to 0 and the function will be throttled:
103 |
104 | 
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 |
--------------------------------------------------------------------------------