Skip to content

Commit

Permalink
Flux oci support 11: add integration test for Unable to use Flux OCI …
Browse files Browse the repository at this point in the history
…with Harbor robot accounts #5219  (#5246)

* added flux integration tests for an artifact repository hosted on Google Cloud Platform

* incremental

* incremental

* added flux integration tests for an artifact repository hosted on Google Cloud Platform

* incremental

* incremental

* incremental

* incremental

* narrow down the list of permissions for harbor robot account
  • Loading branch information
gfichtenholt authored Aug 31, 2022
1 parent 384edcb commit d22a23b
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,14 @@ func TestKindClusterAvailablePackageEndpointsForOCI(t *testing.T) {
t.Fatalf("Environment variables GITHUB_USER and GITHUB_TOKEN need to be set to run this test")
}

if err := setupHarborStefanProdanClone(t); err != nil {
t.Fatal(err)
}
harborRobotName, harborRobotSecret, err := setupHarborRobotAccount(t)
if err != nil {
t.Fatal(err)
}

// ref: https://cloud.google.com/artifact-registry/docs/helm/authentication#token
gcpUser := "oauth2accesstoken"
gcpPasswd, err := gcloudPrintAccessToken(t)
Expand Down Expand Up @@ -599,13 +607,23 @@ func TestKindClusterAvailablePackageEndpointsForOCI(t *testing.T) {
},
*/
{
testName: "Testing [" + harbor_stefanprodan_podinfo_oci_registry_url + "] with basic auth secret",
testName: "Testing [" + harbor_stefanprodan_podinfo_oci_registry_url + "] with basic auth secret (admin)",
registryUrl: harbor_stefanprodan_podinfo_oci_registry_url,
secret: newBasicAuthSecret(types.NamespacedName{
Name: "oci-repo-secret-" + randSeq(4),
Namespace: "default"},
harbor_admin_user,
harbor_admin_pwd,
),
},
{
testName: "Testing [" + harbor_stefanprodan_podinfo_oci_registry_url + "] with basic auth secret (robot)",
registryUrl: harbor_stefanprodan_podinfo_oci_registry_url,
secret: newBasicAuthSecret(types.NamespacedName{
Name: "oci-repo-secret-" + randSeq(4),
Namespace: "default"},
harbor_user,
harbor_pwd,
harborRobotName,
harborRobotSecret,
),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ func (l *dockerRegistryApiV2RepositoryLister) ListRepositoryNames(ociRepo *OCICh
log.Infof("orasRegistry.Repositories fn: %s", repos)
lastRepoMatch := false
for _, r := range repos {
if lastRepoMatch = strings.HasPrefix(r, startAt+"/"); lastRepoMatch {
// Examples:
// GitHub and Harbor: stefanprodan-podinfo-clone/podinfo
// GCP Artifact Repository: vmware-kubeapps-ci/stefanprodan-podinfo-clone/podinfo
lastRepoMatch =
strings.HasPrefix(r, startAt+"/") ||
strings.Contains(r, "/"+startAt+"/")
if lastRepoMatch {
repositoryList = append(repositoryList, r)
}
}
Expand All @@ -102,6 +108,7 @@ func (l *dockerRegistryApiV2RepositoryLister) ListRepositoryNames(ociRepo *OCICh
}

func newRemoteOrasRegistry(ociRepo *OCIChartRepository) (*orasregistryremotev2.Registry, error) {

ref := strings.TrimPrefix(ociRepo.url.String(), fmt.Sprintf("%s://", registry.OCIScheme))
parsedRef, err := orasregistryv2.ParseReference(ref)
if err != nil {
Expand All @@ -116,5 +123,6 @@ func newRemoteOrasRegistry(ociRepo *OCIChartRepository) (*orasregistryremotev2.R
Cache: orasregistryauthv2.DefaultCache,
Credential: ociRepo.registryCredentialFn,
}

return orasRegistry, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ const (
github_gfichtenholt_podinfo_oci_registry_url = "oci://ghcr.io/gfichtenholt/helm-charts"

// admin/Harbor12345 is a well known default login for harbor registries
harbor_host = "demo.goharbor.io"
harbor_user = "admin"
harbor_pwd = "Harbor12345"
harbor_host = "demo.goharbor.io"
harbor_admin_user = "admin"
harbor_admin_pwd = "Harbor12345"
)

func checkEnv(t *testing.T) (fluxplugin.FluxV2PackagesServiceClient, fluxplugin.FluxV2RepositoriesServiceClient, error) {
Expand Down Expand Up @@ -1369,6 +1369,49 @@ func deleteChartFromMyGithubRegistry(t *testing.T, version string) error {
return err
}

func setupHarborStefanProdanClone(t *testing.T) error {
t.Logf("+setupHarborStefanProdanClone()")
defer t.Logf("-setupHarborStefanProdanClone()")

args := []string{
"setupHarborStefanProdanClone",
"--quick",
}

// use the CLI for now
_, err := execCommand(t, "./testdata", "./kind-cluster-setup.sh", args)
return err
}

func setupHarborRobotAccount(t *testing.T) (string, string, error) {
t.Logf("+setupHarborRobotAccount()")
defer t.Logf("-setupHarborRobotAccount()")

args := []string{
"setupHarborRobotAccount",
}

// use the CLI for now
out, err := execCommand(t, "./testdata", "./kind-cluster-setup.sh", args)
if err != nil {
return "", "", err
} else {
i := strings.Index(out, "Robot account successfully created: [")
if i >= 0 {
out2 := out[i+37:]
j := strings.Index(out2, "]")
if j >= 0 {
out3 := out2[:j]
strs := strings.SplitN(out3, " ", 2)
if len(strs) == 2 {
return strs[0], strs[1], nil
}
}
}
return "", "", fmt.Errorf("unexpected response: %s", out)
}
}

// ref https://cloud.google.com/artifact-registry/docs/helm/store-helm-charts#auth-token
// this token lasts 60 mins
func gcloudPrintAccessToken(t *testing.T) (string, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func TestKindClusterAddPackageRepository(t *testing.T) {
},
{
testName: "test add OCI repo from harbor registry with dockerconfigjson secret (kubeapps managed)",
request: add_repo_req_27(harbor_host, harbor_user, harbor_pwd),
request: add_repo_req_27(harbor_host, harbor_admin_user, harbor_admin_pwd),
expectedResponse: add_repo_expected_resp_11,
expectedStatusCode: codes.OK,
},
Expand Down Expand Up @@ -533,7 +533,7 @@ func TestKindClusterGetPackageRepositoryDetail(t *testing.T) {
existingSecret: newDockerConfigJsonSecret(types.NamespacedName{
Name: "secret-1",
Namespace: "TBD",
}, harbor_host, harbor_user, harbor_pwd),
}, harbor_host, harbor_admin_user, harbor_admin_pwd),
expectedStatusCode: codes.OK,
expectedResponse: get_repo_detail_resp_20,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,15 @@ The following service account was set up for use by the integration tests using
Google Cloud Console
- GCP project: `vmware-kubeapps-ci`
- GCP service account:
- Name and ID: `flux-plugin-test-sa-3`
- Name and ID: `flux-plugin-test-sa-4`
- Description: `Service Account for integration testing of kubeapps flux plugin`

Roles granted to this service account:
- Editor. Verified 8/24/22 11:39pm this works.

**TODO** need to reduce permissions to smallest workable set.
Probably some combination of these and others:
- Artifact Registry Administrator
- Artifact Registry Repository Administrator
- Artifact Registry Service Agent
- Viewer

Make sure you see a message "Policy Updated" at the bottom of the screen when you grant these roles.
If you see "Failed to add project roles" or some other error message,
Make sure you see a message "Policy Updated" at the bottom of the screen when you grant these roles. If you see "Failed to add project roles" or some other error message,
create the service account with a different Name/ID
- The service account key file can be downloaded with Google Cloud Console
Under IAM & Admin -> Service Accounts
Expand All @@ -33,11 +28,10 @@ $ export GOOGLE_APPLICATION_CREDENTIALS=..../gcloud-kubeapps-flux-test-sa-key-fi
$ export GCP_TOKEN=$(gcloud auth application-default print-access-token)
```
FYI: GCP token expires an hour after it's issued
FYI: GCP access token expires 1 hour after it's issued

1. check PING is working
* with service account access token

```
$ curl -iL https://us-west1-docker.pkg.dev/v2/ -H "Authorization: Bearer $GCP_TOKEN"
HTTP/2 200
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "$NAME",
"duration": 30,
"description": null,
"disable": false,
"level": "system",
"permissions": [
{
"kind": "project",
"namespace": "$PROJECT_NAME",
"access": [
{
"resource": "repository",
"action": "list"
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ function setupHarborStefanProdanClone {
# this creates a clone of what was out on "oci://ghcr.io/stefanprodan/charts" as of Jul 28 2022
# to oci://demo.goharbor.io/stefanprodan-podinfo-clone
local PROJECT_NAME=stefanprodan-podinfo-clone

if [[ "$1" == "--quick" ]]; then
# short to only look at the project existence and if so assume all is well
echo
echo -e Checking if harbor project [${L_YELLOW}$PROJECT_NAME${NC}] exists...
local status_code=$(curl -L --write-out %{http_code} \
--silent --output /dev/null \
--show-error \
--head ${FLUX_TEST_HARBOR_URL}/api/v2.0/projects?project_name=${PROJECT_NAME} \
-u $FLUX_TEST_HARBOR_ADMIN_USER:$FLUX_TEST_HARBOR_ADMIN_PWD)
if [[ "$status_code" -eq 200 ]] ; then
echo -e "Project [${L_YELLOW}$PROJECT_NAME${NC}] exists in harbor."
exit 0
fi
fi

deleteHarborProject $PROJECT_NAME
createHarborProject $PROJECT_NAME

Expand All @@ -124,3 +140,66 @@ function setupHarborStefanProdanClone {
echo TODO
echo
}

function deleteHarborRobotAccount()
{
# sanity check
if [[ "$#" -lt 1 ]]; then
error_exit "Usage: deleteHarborRobotAccount name"
fi
local ACCOUNT_NAME=$1
echo
echo -e Checking if harbor robot account [${L_YELLOW}$ACCOUNT_NAME${NC}] exists...
local CMD="curl -L --silent --show-error \
${FLUX_TEST_HARBOR_URL}/api/v2.0/robots \
-u $FLUX_TEST_HARBOR_ADMIN_USER:$FLUX_TEST_HARBOR_ADMIN_PWD"
local RESP=$($CMD)
local ID=$(echo "$RESP" | jq --arg NAME "robot\$$ACCOUNT_NAME" '.[] | select(.name == $NAME) | .id')
if [[ "$ID" != "" ]] ; then
echo -e "Deleting robot account [${L_YELLOW}$ACCOUNT_NAME${NC}] in harbor..."
status_code=$(curl -L --write-out %{http_code} --silent \
--show-error -X DELETE --output /dev/null \
${FLUX_TEST_HARBOR_URL}/api/v2.0/robots/$ID \
-u $FLUX_TEST_HARBOR_ADMIN_USER:$FLUX_TEST_HARBOR_ADMIN_PWD)
if [[ "$status_code" -eq 200 ]] ; then
echo -e Robot account [${L_YELLOW}$ACCOUNT_NAME${NC}] deleted
else
error_exit "Failed to delete robot account [$ACCOUNT_NAME] due to HTTP status: [$status_code]"
fi
fi
}

function createHarborRobotAccount()
{
# sanity check
if [[ "$#" -lt 2 ]]; then
error_exit "Usage: createHarborRobotAccount name project_name"
fi
local ACCOUNT_NAME=$1
local PROJECT_NAME=$2

echo -e "Creating robot account [${L_YELLOW}$ACCOUNT_NAME${NC}] in harbor..."
local payload=$(sed "s/\$NAME/${ACCOUNT_NAME}/g" $SCRIPTPATH/harbor-create-robot-account.json)
payload=$(echo $payload | sed "s/\$PROJECT_NAME/${PROJECT_NAME}/g")
local RESP=$(curl -L --silent --show-error \
-X POST \
-H 'Content-Type: application/json' \
--data "${payload}" \
${FLUX_TEST_HARBOR_URL}/api/v2.0/robots \
-u $FLUX_TEST_HARBOR_ADMIN_USER:$FLUX_TEST_HARBOR_ADMIN_PWD)
local RESP2=$(echo "$RESP" | jq -r '. | {name,secret} | join(" ")')
if [[ "$RESP2" == robot* ]] ; then
echo -e "Robot account successfully created: [$RESP2]"
else
error_exit "Unexpected HTTP response creating robot account [$ACCOUNT_NAME]: $RESP"
fi
}

function setupHarborRobotAccount()
{
local ACCOUNT_NAME=kubeapps-flux-plugin
local PROJECT_NAME=stefanprodan-podinfo-clone

deleteHarborRobotAccount $ACCOUNT_NAME
createHarborRobotAccount $ACCOUNT_NAME $PROJECT_NAME
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,9 @@ deleteChartVersionFromMyGitHub) deleteChartVersionFromMyGitHubRegistry $2
;;
setupGithubStefanProdanClone) setupGithubStefanProdanClone
;;
setupHarborStefanProdanClone) setupHarborStefanProdanClone
setupHarborStefanProdanClone) setupHarborStefanProdanClone $2
;;
setupHarborRobotAccount) setupHarborRobotAccount
;;
setupGcrStefanProdanClone) setupGcrStefanProdanClone
;;
Expand Down

0 comments on commit d22a23b

Please sign in to comment.