add github&gitlab reference support to generate-key-pair (#848)
* feat: add github support to generate-key-pair command * feat: gitlab integration * feat: add github/gitlab to generate-key-pair command's description Signed-off-by: Erkan Zileli <erkan.zileli@trendyol.com> Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com> Co-authored-with: Batuhan Apaydın <batuhan.apaydin@trendyol.com> Co-authored-with: Furkan Türkal <furkan.turkal@trendyol.com>
This commit is contained in:
@@ -21,8 +21,12 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sigstore/cosign/pkg/cosign/git"
|
||||
"github.com/sigstore/cosign/pkg/cosign/git/github"
|
||||
"github.com/sigstore/cosign/pkg/cosign/git/gitlab"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
@@ -57,8 +61,24 @@ func GenerateKeyPairCmd(ctx context.Context, kmsVal string, args []string) error
|
||||
fmt.Fprintln(os.Stderr, "Public key written to cosign.pub")
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
return kubernetes.KeyPairSecret(ctx, args[0], GetPass)
|
||||
split := strings.Split(args[0], "://")
|
||||
|
||||
if len(split) < 2 {
|
||||
return errors.New("could not parse scheme, use <scheme>://<ref> format")
|
||||
}
|
||||
|
||||
provider, targetRef := split[0], split[1]
|
||||
|
||||
switch provider {
|
||||
case "k8s":
|
||||
return kubernetes.KeyPairSecret(ctx, targetRef, GetPass)
|
||||
case gitlab.ReferenceScheme, github.ReferenceScheme:
|
||||
return git.GetProvider(provider).PutSecret(ctx, targetRef, GetPass)
|
||||
}
|
||||
|
||||
return fmt.Errorf("undefined provider: %s", provider)
|
||||
}
|
||||
|
||||
keys, err := cosign.GenerateKeyPair(GetPass)
|
||||
|
||||
@@ -49,6 +49,15 @@ func addGenerateKeyPair(topLevel *cobra.Command) {
|
||||
# generate a key-pair in Kubernetes Secret
|
||||
cosign generate-key-pair k8s://[NAMESPACE]/[NAME]
|
||||
|
||||
# generate a key-pair in GitHub
|
||||
cosign generate-key-pair github://[OWNER]/[PROJECT_NAME]
|
||||
|
||||
# generate a key-pair in GitLab with project name
|
||||
cosign generate-key-pair gitlab://[OWNER]/[PROJECT_NAME]
|
||||
|
||||
# generate a key-pair in GitLab with project id
|
||||
cosign generate-key-pair gitlab://[PROJECT_ID]
|
||||
|
||||
CAVEATS:
|
||||
This command interactively prompts for a password. You can use
|
||||
the COSIGN_PASSWORD environment variable to provide one.`,
|
||||
|
||||
9
doc/cosign_generate-key-pair.md
generated
9
doc/cosign_generate-key-pair.md
generated
@@ -33,6 +33,15 @@ cosign generate-key-pair [flags]
|
||||
# generate a key-pair in Kubernetes Secret
|
||||
cosign generate-key-pair k8s://[NAMESPACE]/[NAME]
|
||||
|
||||
# generate a key-pair in GitHub
|
||||
cosign generate-key-pair github://[OWNER]/[PROJECT_NAME]
|
||||
|
||||
# generate a key-pair in GitLab with project name
|
||||
cosign generate-key-pair gitlab://[OWNER]/[PROJECT_NAME]
|
||||
|
||||
# generate a key-pair in GitLab with project id
|
||||
cosign generate-key-pair gitlab://[PROJECT_ID]
|
||||
|
||||
CAVEATS:
|
||||
This command interactively prompts for a password. You can use
|
||||
the COSIGN_PASSWORD environment variable to provide one.
|
||||
|
||||
3
go.mod
3
go.mod
@@ -38,6 +38,7 @@ require (
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.1 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211004163346-9ae11fe20941
|
||||
github.com/google/go-github/v39 v39.1.0
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/onsi/gomega v1.16.0 // indirect
|
||||
@@ -46,9 +47,11 @@ require (
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.1.0
|
||||
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613
|
||||
github.com/urfave/cli v1.22.5 // indirect
|
||||
github.com/xanzy/go-gitlab v0.31.0
|
||||
go.opentelemetry.io/contrib v0.24.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.9.0 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
k8s.io/klog/v2 v2.20.0 // indirect
|
||||
|
||||
5
go.sum
5
go.sum
@@ -896,8 +896,12 @@ github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211004163346-
|
||||
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211004163346-9ae11fe20941/go.mod h1:j3IqhBG3Ox1NXmmhbWU4UmiHVAf2dUgB7le1Ch7JZQ0=
|
||||
github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0=
|
||||
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
|
||||
github.com/google/go-github/v39 v39.1.0 h1:1vf4gM0D1e+Df2HMxaYC3+o9+Huj3ywGTtWc3VVYaDA=
|
||||
github.com/google/go-github/v39 v39.1.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
|
||||
github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
|
||||
github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
|
||||
github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
|
||||
@@ -1655,6 +1659,7 @@ github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1
|
||||
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
||||
github.com/xanzy/go-gitlab v0.31.0 h1:+nHztQuCXGSMluKe5Q9IRaPdz6tO8O0gMkQ0vqGpiBk=
|
||||
github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
|
||||
37
pkg/cosign/git/git.go
Normal file
37
pkg/cosign/git/git.go
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
"github.com/sigstore/cosign/pkg/cosign/git/github"
|
||||
"github.com/sigstore/cosign/pkg/cosign/git/gitlab"
|
||||
)
|
||||
|
||||
var providerMap = map[string]Git{
|
||||
github.ReferenceScheme: github.New(),
|
||||
gitlab.ReferenceScheme: gitlab.New(),
|
||||
}
|
||||
|
||||
type Git interface {
|
||||
PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error
|
||||
}
|
||||
|
||||
func GetProvider(provider string) Git {
|
||||
return providerMap[provider]
|
||||
}
|
||||
119
pkg/cosign/git/github/github.go
Normal file
119
pkg/cosign/git/github/github.go
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
ReferenceScheme = "github"
|
||||
)
|
||||
|
||||
type Gh struct{}
|
||||
|
||||
func New() *Gh {
|
||||
return &Gh{}
|
||||
}
|
||||
|
||||
func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error {
|
||||
keys, err := cosign.GenerateKeyPair(pf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generating key pair")
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
if token, ok := os.LookupEnv("GITHUB_TOKEN"); ok {
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
httpClient = oauth2.NewClient(ctx, ts)
|
||||
} else {
|
||||
return errors.New("could not find \"GITHUB_TOKEN\" env variable")
|
||||
}
|
||||
client := github.NewClient(httpClient)
|
||||
|
||||
split := strings.Split(ref, "/")
|
||||
if len(split) < 2 {
|
||||
return errors.New("could not parse scheme, use github://<owner>/<repo> format")
|
||||
}
|
||||
owner, repo := split[0], split[1]
|
||||
|
||||
key, getRepoPubKeyResp, err := client.Actions.GetRepoPublicKey(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get repository public key")
|
||||
}
|
||||
|
||||
if getRepoPubKeyResp.StatusCode < 200 && getRepoPubKeyResp.StatusCode >= 300 {
|
||||
bodyBytes, _ := io.ReadAll(getRepoPubKeyResp.Body)
|
||||
return fmt.Errorf("%s", bodyBytes)
|
||||
}
|
||||
|
||||
passwordSecretEnv := &github.EncryptedSecret{
|
||||
Name: "COSIGN_PASSWORD",
|
||||
KeyID: key.GetKeyID(),
|
||||
EncryptedValue: base64.StdEncoding.EncodeToString(keys.Password()),
|
||||
}
|
||||
|
||||
passwordSecretEnvResp, err := client.Actions.CreateOrUpdateRepoSecret(ctx, owner, repo, passwordSecretEnv)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create \"COSIGN_PASSWORD\" environment variable")
|
||||
}
|
||||
|
||||
if passwordSecretEnvResp.StatusCode < 200 && passwordSecretEnvResp.StatusCode >= 300 {
|
||||
bodyBytes, _ := io.ReadAll(passwordSecretEnvResp.Body)
|
||||
return fmt.Errorf("%s", bodyBytes)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Private key written to COSIGN_PASSWORD environment variable")
|
||||
|
||||
privateKeySecretEnv := &github.EncryptedSecret{
|
||||
Name: "COSIGN_PRIVATE_KEY",
|
||||
KeyID: key.GetKeyID(),
|
||||
EncryptedValue: base64.StdEncoding.EncodeToString(keys.PrivateBytes),
|
||||
}
|
||||
|
||||
repoSecretEnvResp, err := client.Actions.CreateOrUpdateRepoSecret(ctx, owner, repo, privateKeySecretEnv)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create \"COSIGN_PRIVATE_KEY\" environment variable")
|
||||
}
|
||||
|
||||
if repoSecretEnvResp.StatusCode < 200 && repoSecretEnvResp.StatusCode >= 300 {
|
||||
bodyBytes, _ := io.ReadAll(repoSecretEnvResp.Body)
|
||||
return fmt.Errorf("%s", bodyBytes)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Private key written to COSIGN_PRIVATE_KEY environment variable")
|
||||
|
||||
if err := ioutil.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Public key written to cosign.pub")
|
||||
|
||||
return nil
|
||||
}
|
||||
108
pkg/cosign/git/gitlab/gitlab.go
Normal file
108
pkg/cosign/git/gitlab/gitlab.go
Normal file
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
const (
|
||||
ReferenceScheme = "gitlab"
|
||||
)
|
||||
|
||||
type Gl struct{}
|
||||
|
||||
func New() *Gl {
|
||||
return &Gl{}
|
||||
}
|
||||
|
||||
func (g *Gl) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error {
|
||||
keys, err := cosign.GenerateKeyPair(pf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generating key pair")
|
||||
}
|
||||
|
||||
token, tokenExists := os.LookupEnv("GITLAB_TOKEN")
|
||||
|
||||
if !tokenExists {
|
||||
return errors.New("could not find \"GITLAB_TOKEN\"")
|
||||
}
|
||||
|
||||
var client *gitlab.Client
|
||||
if url, baseURLExists := os.LookupEnv("GITLAB_BASE_URL"); baseURLExists {
|
||||
client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create GitLab client")
|
||||
}
|
||||
} else {
|
||||
client, err = gitlab.NewClient(token)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create GitLab client")
|
||||
}
|
||||
}
|
||||
|
||||
_, passwordResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{
|
||||
Key: gitlab.String("COSIGN_PASSWORD"),
|
||||
Value: gitlab.String(string(keys.Password())),
|
||||
VariableType: gitlab.VariableType(gitlab.EnvVariableType),
|
||||
Protected: gitlab.Bool(false),
|
||||
Masked: gitlab.Bool(false),
|
||||
EnvironmentScope: gitlab.String("*"),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create \"COSIGN_PASSWORD\" variable")
|
||||
}
|
||||
|
||||
if passwordResp.StatusCode < 200 && passwordResp.StatusCode >= 300 {
|
||||
bodyBytes, _ := io.ReadAll(passwordResp.Body)
|
||||
return errors.Errorf("%s", bodyBytes)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Password written to \"COSIGN_PASSWORD\" variable")
|
||||
|
||||
_, privateKeyResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{
|
||||
Key: gitlab.String("COSIGN_PRIVATE_KEY"),
|
||||
Value: gitlab.String(string(keys.PrivateBytes)),
|
||||
VariableType: gitlab.VariableType(gitlab.EnvVariableType),
|
||||
Protected: gitlab.Bool(false),
|
||||
Masked: gitlab.Bool(false),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create \"COSIGN_PRIVATE_KEY\" variable")
|
||||
}
|
||||
|
||||
if privateKeyResp.StatusCode < 200 && privateKeyResp.StatusCode >= 300 {
|
||||
bodyBytes, _ := io.ReadAll(privateKeyResp.Body)
|
||||
return errors.Errorf("%s", bodyBytes)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Private key written to \"COSIGN_PRIVATE_KEY\" variable")
|
||||
|
||||
if err := ioutil.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Public key written to cosign.pub")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -29,7 +29,9 @@ import (
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
)
|
||||
|
||||
const KeyReference = "k8s://"
|
||||
const (
|
||||
KeyReference = "k8s://"
|
||||
)
|
||||
|
||||
func GetKeyPairSecret(ctx context.Context, k8sRef string) (*v1.Secret, error) {
|
||||
namespace, name, err := parseRef(k8sRef)
|
||||
|
||||
Reference in New Issue
Block a user