Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
feat: Add SSH publicKey auth support (#6855)
Browse files Browse the repository at this point in the history
* feat: Add SSH publicKey auth support

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* updated shipyard-controller

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* first vesion working

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* added tests

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* refactor

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* add integration test

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* remove unnecesarry check from integration test

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* fix unit tests

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* remove flaky tests

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* minor fix - rebase to master

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* fixed spelling

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* refactor

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* fixed sonarcloud

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>

* refactor

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>
  • Loading branch information
odubajDT authored Feb 23, 2022
1 parent 6ab050d commit b1b3d11
Show file tree
Hide file tree
Showing 35 changed files with 560 additions and 162 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,18 @@ jobs:
helm install --values test/assets/gitea/values.yaml gitea gitea-charts/gitea -n ${KEPTN_NAMESPACE} --wait --version v5.0.0
GITEA_ADMIN_USER=$(kubectl get pod -n ${KEPTN_NAMESPACE} gitea-0 -ojsonpath='{@.spec.initContainers[?(@.name=="configure-gitea")].env[?(@.name=="GITEA_ADMIN_USERNAME")].value}')
GITEA_ADMIN_PASSWORD=$(kubectl get pod -n ${KEPTN_NAMESPACE} gitea-0 -ojsonpath='{@.spec.initContainers[?(@.name=="configure-gitea")].env[?(@.name=="GITEA_ADMIN_PASSWORD")].value}')
ssh-keygen -t rsa -C "gitea-http" -f "rsa_gitea" -P "myGiteaPassPhrase"
GITEA_PRIVATE_KEY=$(cat rsa_gitea)
GITEA_PUBLIC_KEY=$(cat rsa_gitea.pub)
GITEA_PRIVATE_KEY_PASSPHRASE=myGiteaPassPhrase
sleep 30 # TODO
kubectl port-forward -n ${KEPTN_NAMESPACE} svc/gitea-http 3000:3000 &
kubectl port-forward -n ${KEPTN_NAMESPACE} svc/gitea-ssh 3001:22 &
sleep 30 # TODO
curl -vkL --silent --user ${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD} -X POST "http://localhost:3000/api/v1/users/${GITEA_ADMIN_USER}/tokens" -H "accept: application/json" -H "Content-Type: application/json; charset=utf-8" -d "{ \"name\": \"my-token\" }" -o gitea-token.txt
curl -vkL --silent --user ${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD} -X POST "http://localhost:3000/api/v1/user/keys" -H "accept: application/json" -H "Content-Type: application/json; charset=utf-8" -d "{ \"key\": \"$GITEA_PUBLIC_KEY\", \"title\": \"public-key-gitea\"}"
GITEA_TOKEN=$(cat gitea-token.txt | jq -r .sha1)
kubectl create secret generic gitea-access -n ${KEPTN_NAMESPACE} --from-literal=username=${GITEA_ADMIN_USER} --from-literal=password=${GITEA_TOKEN}
kubectl create secret generic gitea-access -n ${KEPTN_NAMESPACE} --from-literal=username=${GITEA_ADMIN_USER} --from-literal=password=${GITEA_TOKEN} --from-literal=private-key="${GITEA_PRIVATE_KEY}" --from-literal=private-key-pass=${GITEA_PRIVATE_KEY_PASSPHRASE}
rm gitea-token.txt
- name: Expose Keptn API (Minishift)
Expand Down
61 changes: 50 additions & 11 deletions cli/cmd/create_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"strings"
"time"

"github.com/keptn/keptn/cli/internal"
Expand All @@ -18,19 +20,27 @@ import (
)

type createProjectCmdParams struct {
Shipyard *string
GitUser *string
GitToken *string
RemoteURL *string
Shipyard *string
GitUser *string
GitToken *string
RemoteURL *string
GitPrivateKey *string
GitPrivateKeyPass *string
}

var createProjectParams *createProjectCmdParams

const gitErrMsg = `Please specify a 'git-user', 'git-token', and 'git-remote-url' as flags for configuring a Git upstream repository`
const gitErrMsg = `Please specify a 'git-user' and 'git-remote-url' as flags for configuring a Git upstream repository together with 'git-token' or 'git-private-key' depending on auth method. Please be aware that authentication with public/private key is supported only when using resource-service.`
const gitMissingUpstream = `WARNING: Creating a project without Git upstream repository is not recommended and will not be supported in the future anymore.
You can configure a Git upstream repository using:
keptn update project PROJECTNAME --git-user=GIT_USER --git-token=GIT_TOKEN --git-remote-url=GIT_REMOTE_URL
keptn update project PROJECTNAME --git-user=GIT_USER --git-remote-url=GIT_REMOTE_URL --git-token=GIT_TOKEN
or (only for resource-service)
keptn update project PROJECTNAME --git-user=GIT_USER --git-remote-url=GIT_REMOTE_URL --git-private-key=PRIVATE_KEY_PATH --git-private-key-pass=PRIVATE_KEY_PASSPHRASE
Please be aware that authentication with public/private key is supported only when using resource-service.
`

// crProjectCmd represents the project command
Expand All @@ -41,12 +51,19 @@ var crProjectCmd = &cobra.Command{
The shipyard file describes the used stages. These stages are defined by name, as well as their task sequences.
By executing the *create project* command, Keptn initializes an internal Git repository that is used to maintain all project-related resources.
To upstream this internal Git repository to a remote repository, the Git user (*--git-user*), an access token (*--git-token*), and the remote URL (*--git-remote-url*) are required.
To upstream this internal Git repository to a remote repository, the Git user (*--git-user*) and the remote URL (*--git-remote-url*) are required
together with private key (*--git-private-key*) or access token (*--git-token*). Please be aware that authentication with public/private key is
supported only when using resource-service.
For more information about Shipyard, creating projects, or upstream repositories, please go to [Manage Keptn](https://keptn.sh/docs/` + getReleaseDocsURL() + `/manage/)
`,
Example: `keptn create project PROJECTNAME --shipyard=FILEPATH
keptn create project PROJECTNAME --shipyard=FILEPATH --git-user=GIT_USER --git-token=GIT_TOKEN --git-remote-url=GIT_REMOTE_URL`,
keptn create project PROJECTNAME --shipyard=FILEPATH --git-user=GIT_USER --git-remote-url=GIT_REMOTE_URL --git-token=GIT_TOKEN
or (only for resource-service)
keptn create project PROJECTNAME --git-user=GIT_USER --git-remote-url=GIT_REMOTE_URL --git-private-key=PRIVATE_KEY_PATH --git-private-key-pass=PRIVATE_KEY_PASSPHRASE
`,
SilenceUsage: true,
Args: func(cmd *cobra.Command, args []string) error {
_, _, err := credentialmanager.NewCredentialManager(assumeYes).GetCreds(namespace)
Expand Down Expand Up @@ -86,10 +103,23 @@ keptn create project PROJECTNAME --shipyard=FILEPATH --git-user=GIT_USER --git-t
Shipyard: &encodedShipyardContent,
}

if *createProjectParams.GitUser != "" && *createProjectParams.GitToken != "" && *createProjectParams.RemoteURL != "" {
if *createProjectParams.GitUser != "" && *createProjectParams.RemoteURL != "" {
if *createProjectParams.GitToken == "" && *createProjectParams.GitPrivateKey == "" {
return errors.New(gitErrMsg)
}

project.GitUser = *createProjectParams.GitUser
project.GitToken = *createProjectParams.GitToken
project.GitRemoteURL = *createProjectParams.RemoteURL

if strings.HasPrefix(*createProjectParams.RemoteURL, "ssh://") {
content, err := ioutil.ReadFile(*createProjectParams.GitPrivateKey)
if err != nil {
fmt.Errorf("unable to read privateKey file: %s\n", err.Error())
}
project.GitPrivateKey = string(content)
project.GitPrivateKeyPass = *createProjectParams.GitPrivateKeyPass
}
}

api, err := internal.APIProvider(endPoint.String(), apiToken)
Expand Down Expand Up @@ -121,9 +151,14 @@ func checkGitCredentials() error {
return nil
}

if *createProjectParams.GitUser != "" && *createProjectParams.GitToken != "" && *createProjectParams.RemoteURL != "" {
if *createProjectParams.GitToken != "" && *createProjectParams.GitPrivateKey != "" {
return errors.New(gitErrMsg)
}

if *createProjectParams.GitUser != "" && *createProjectParams.RemoteURL != "" {
return nil
}

return errors.New(gitErrMsg)
}

Expand All @@ -148,6 +183,10 @@ func init() {
crProjectCmd.MarkFlagRequired("shipyard")

createProjectParams.GitUser = crProjectCmd.Flags().StringP("git-user", "u", "", "The git user of the upstream target")
createProjectParams.GitToken = crProjectCmd.Flags().StringP("git-token", "t", "", "The git token of the git user")
createProjectParams.RemoteURL = crProjectCmd.Flags().StringP("git-remote-url", "r", "", "The remote url of the upstream target")

createProjectParams.GitToken = crProjectCmd.Flags().StringP("git-token", "t", "", "The git token of the git user")

createProjectParams.GitPrivateKey = crProjectCmd.Flags().StringP("git-private-key", "k", "", "The SSH git private key of the git user")
createProjectParams.GitPrivateKeyPass = crProjectCmd.Flags().StringP("git-private-key-pass", "l", "", "The passphrase of git private key")
}
15 changes: 15 additions & 0 deletions cli/cmd/create_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,18 @@ func TestCreateProjectUnknownCommand(t *testing.T) {
func TestCreateProjectUnknownParmeter(t *testing.T) {
testInvalidInputHelper("create project sockshop --projectt=sockshop", "unknown flag: --projectt", t)
}

// TestCreateProjectCmdTokenAndKey
func TestCreateProjectCmdTokenAndKey(t *testing.T) {
credentialmanager.MockAuthCreds = true

shipyardFilePath := "./shipyard.yaml"
defer testShipyard(t, shipyardFilePath, "")()

cmd := fmt.Sprintf("create project sockshop --shipyard=%s --git-user=user--git-remote-url=https://someurl.com --git-private-key=key --git-token=token", shipyardFilePath)
_, err := executeActionCommandC(cmd)

if !errorContains(err, gitErrMsg) {
t.Errorf("missing expected error, but got %v", err)
}
}
46 changes: 38 additions & 8 deletions cli/cmd/update_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"errors"
"fmt"
"io/ioutil"
"strings"

"github.com/keptn/keptn/cli/internal"

Expand All @@ -15,9 +17,11 @@ import (
)

type updateProjectCmdParams struct {
GitUser *string
GitToken *string
RemoteURL *string
GitUser *string
GitToken *string
RemoteURL *string
GitPrivateKey *string
GitPrivateKeyPass *string
}

var updateProjectParams *updateProjectCmdParams
Expand All @@ -31,11 +35,17 @@ var upProjectCmd = &cobra.Command{
Updating a shipyard file is not possible.
By executing the update project command, Keptn will add the provided upstream repository to the existing internal Git repository that is used to maintain all project-related resources.
To upstream this internal Git repository to a remote repository, the Git user (--git-user), an access token (--git-token), and the remote URL (--git-remote-url) are required.
To upstream this internal Git repository to a remote repository, the Git user (--git-user) and the remote URL (*--git-remote-url*) are required
together with private key (*--git-private-key*) or access token (*--git-token*). Please be aware that authentication with public/private key is
supported only when using resource-service.
For more information about updating projects or upstream repositories, please go to [Manage Keptn](https://keptn.sh/docs/` + getReleaseDocsURL() + `/manage/)
`,
Example: `keptn update project PROJECTNAME --git-user=GIT_USER --git-token=GIT_TOKEN --git-remote-url=GIT_REMOTE_URL`,
Example: `keptn update project PROJECTNAME --git-user=GIT_USER --git-token=GIT_TOKEN --git-remote-url=GIT_REMOTE_URL
or (only for resource-service)
keptn update project PROJECTNAME --git-user=GIT_USER --git-remote-url=GIT_REMOTE_URL --git-private-key=PRIVATE_KEY_PATH --git-private-key-pass=PRIVATE_KEY_PASSPHRASE`,
SilenceUsage: true,
Args: func(cmd *cobra.Command, args []string) error {
_, _, err := credentialmanager.NewCredentialManager(assumeYes).GetCreds(namespace)
Expand Down Expand Up @@ -71,10 +81,27 @@ For more information about updating projects or upstream repositories, please go
Name: &args[0],
}

if *updateProjectParams.GitUser != "" && *updateProjectParams.GitToken != "" && *updateProjectParams.RemoteURL != "" {
if *updateProjectParams.GitUser != "" && *updateProjectParams.RemoteURL != "" {
if *updateProjectParams.GitToken == "" && *updateProjectParams.GitPrivateKey == "" {
return errors.New("Access token or private key must be set")
}

if *updateProjectParams.GitToken != "" && *updateProjectParams.GitPrivateKey != "" {
return errors.New("Access token or private key cannot be set together")
}

project.GitUser = *updateProjectParams.GitUser
project.GitToken = *updateProjectParams.GitToken
project.GitRemoteURL = *updateProjectParams.RemoteURL

if strings.HasPrefix(*updateProjectParams.RemoteURL, "ssh://") {
content, err := ioutil.ReadFile(*updateProjectParams.GitPrivateKey)
if err != nil {
fmt.Errorf("unable to read privateKey file: %s\n", err.Error())
}
project.GitPrivateKey = string(content)
project.GitPrivateKeyPass = *updateProjectParams.GitPrivateKeyPass
}
}

api, err := internal.APIProvider(endPoint.String(), apiToken)
Expand Down Expand Up @@ -105,8 +132,11 @@ func init() {

updateProjectParams.GitUser = upProjectCmd.Flags().StringP("git-user", "u", "", "The git user of the upstream target")
updateProjectParams.GitToken = upProjectCmd.Flags().StringP("git-token", "t", "", "The git token of the git user")
updateProjectParams.RemoteURL = upProjectCmd.Flags().StringP("git-remote-url", "r", "", "The remote url of the upstream target")
upProjectCmd.MarkFlagRequired("git-user")
upProjectCmd.MarkFlagRequired("git-token")
upProjectCmd.MarkFlagRequired("git-remote-url")

updateProjectParams.RemoteURL = upProjectCmd.Flags().StringP("git-remote-url", "r", "", "The remote url of the upstream target")

updateProjectParams.GitPrivateKey = upProjectCmd.Flags().StringP("git-private-key", "k", "", "The SSH git private key of the git user")
updateProjectParams.GitPrivateKeyPass = upProjectCmd.Flags().StringP("git-private-key-pass", "l", "", "The passphrase of git private key")
}
12 changes: 12 additions & 0 deletions cli/cmd/update_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,15 @@ func TestUpdateProjectUnknownCommand(t *testing.T) {
func TestUpdateProjectUnknownParmeter(t *testing.T) {
testInvalidInputHelper("update project sockshop --git-userr=GIT_USER --git-token=GIT_TOKEN --git-remote-url=GIT_REMOTE_URL", "unknown flag: --git-userr", t)
}

// TestUpdateProjectCmdTokenAndKey
func TestUpdateProjectCmdTokenAndKey(t *testing.T) {
credentialmanager.MockAuthCreds = true

cmd := fmt.Sprintf("update project sockshop --git-user=user --git-remote-url=https://someurl.com --mock --git-private-key=key --git-token=token")
_, err := executeActionCommandC(cmd)

if !errorContains(err, "Access token or private key cannot be set together") {
t.Errorf("missing expected error, but got %v", err)
}
}
31 changes: 26 additions & 5 deletions resource-service/common/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package common

import (
"fmt"
"os"
"testing"

"github.com/keptn/keptn/resource-service/common_models"
"github.com/keptn/keptn/resource-service/errors"
"github.com/stretchr/testify/require"
Expand All @@ -10,8 +13,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
"os"
"testing"
)

func TestK8sCredentialReader_ReadSecret(t *testing.T) {
Expand All @@ -26,7 +27,7 @@ func TestK8sCredentialReader_ReadSecret(t *testing.T) {
require.Equal(t, &common_models.GitCredentials{
User: "user",
Token: "token",
RemoteURI: "uri",
RemoteURI: "https://my-repo",
}, secret)
}

Expand Down Expand Up @@ -69,7 +70,7 @@ func TestK8sCredentialReader_ReadSecretNoToken(t *testing.T) {
Namespace: "keptn",
},
Data: map[string][]byte{
"git-credentials": []byte(`{"user":"user","token":"","remoteURI":"uri"}`)},
"git-credentials": []byte(`{"user":"user","token":"","remoteURI":"https://some.url"}`)},
Type: corev1.SecretTypeOpaque,
},
))
Expand All @@ -80,6 +81,26 @@ func TestK8sCredentialReader_ReadSecretNoToken(t *testing.T) {
require.Nil(t, secret)
}

func TestK8sCredentialReader_ReadSecretNoPrivateKey(t *testing.T) {
_ = os.Setenv("POD_NAMESPACE", "keptn")
secretReader := NewK8sCredentialReader(fake.NewSimpleClientset(
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "git-credentials-my-project",
Namespace: "keptn",
},
Data: map[string][]byte{
"git-credentials": []byte(`{"user":"user","privateKey":"","remoteURI":"ssh://some.url"}`)},
Type: corev1.SecretTypeOpaque,
},
))

secret, err := secretReader.GetCredentials("my-project")

require.ErrorIs(t, err, errors.ErrCredentialsPrivateKeyMustNotBeEmpty)
require.Nil(t, secret)
}

func TestK8sCredentialReader_ReadSecretError(t *testing.T) {
_ = os.Setenv("POD_NAMESPACE", "keptn")

Expand All @@ -103,7 +124,7 @@ func getK8sSecret() *corev1.Secret {
Namespace: "keptn",
},
Data: map[string][]byte{
"git-credentials": []byte(`{"user":"user","token":"token","remoteURI":"uri"}`)},
"git-credentials": []byte(`{"user":"user","token":"token","remoteURI":"https://my-repo"}`)},
Type: corev1.SecretTypeOpaque,
}
}
Loading

0 comments on commit b1b3d11

Please sign in to comment.