argoproj / argo-cd

Declarative Continuous Deployment for Kubernetes
https://argo-cd.readthedocs.io
Apache License 2.0
18.01k stars 5.49k forks source link

RepositoryService GetHelmCharts: authorization not working for project scoped repositories #20767

Closed carlbalmer closed 1 week ago

carlbalmer commented 1 week ago

Checklist:

Describe the bug

We use the GET api/v1/repositories/{repo}/helmcharts endpoint to query available helm charts for project scoped helm repositories. After an argoCD update from v2.9.21+1804306 to v2.12.6+4dab5bd the endpoint started returning a 'permission denied' error.

Example:

$ curl 'https://argocd.server/api/v1/repositories/https%253A%252F%252Fhelm.server%252Fsome%252Fpath%252F/helmcharts' -H "Authorization: Bearer $TOKEN"
{"error":"permission denied: repositories, get, https://helm.server/some/path/, sub: proj:myproject:myrole, iat: XXX-XX-XXTXX:XX:XXZ","code":7,"message":"permission denied: repositories, get, https://helm.server/some/path/, sub: proj:myproject:myrole, iat: XXX-XX-XXTXX:XX:XXZ"}

If we prefix the project name to the helm repo (the same way it is defined in the RBAC role) the permission gets granted but argoCD tries to use the whole string ('myproject/https://helm.server/some/path/' in this case) as the URL and therefore fails to query the repo.

Example:

$ curl 'https://argocd.server/api/v1/repositories/myproject%252Fhttps%253A%252F%252Fhelm.server%252Fsome%252Fpath%252F/helmcharts' -H "Authorization: Bearer $TOKEN"
{"error":"Get \"myproject/https://helm.server/some/path/index.yaml\": unsupported protocol scheme \"\"","code":2,"message":"Get \"myproject/https://helm.server/some/path/index.yaml\": unsupported protocol scheme \"\""}

To Reproduce Create App Project:

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: myproject
  namespace: argocd
spec:
  destinations:
    - ...
  roles:
    - name: myrole
      policies:
        - 'p, proj:myproject:myrole, repositories, get, myproject/*, allow'
  sourceRepos:
    - 'https://helm.server/some/path/mychart'

Create Repository Secret:

apiVersion: v1
kind: Secret
metadata:
  name: myproject-helm-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
type: Opaque
stringData:
  type: helm
  name: myproject-charts
  url: https://helm.server/some/path/
  project: myproject

Generate a token for the role in the frontend den curl the helm repo:

https://argocd.server/api/v1/repositories/https%253A%252F%252Fhelm.server%252Fsome%252Fpath%252F/helmcharts' -H "Authorization: Bearer $TOKEN

Expected behavior

The request to GET api/v1/repositories/{repo}/helmcharts should return a list of available charts ({"items":[...]}). {repo} should be the repository url (as displayed by argocd repo list) even for project scoped repos.

Version

$ argocd version
argocd: v2.12.6+4dab5bd
  BuildDate: 2024-10-22T15:24:17Z
  GitCommit: 4dab5bd6a60adea12e084ad23519e35b710060a2
  GitTreeState: clean
  GoVersion: go1.22.7 (Red Hat 1.22.7-1.module+el8.10.0+22325+dc584f75)
  Compiler: gc
  Platform: linux/amd64
  ExtraBuildInfo: {Vendor Information: Red Hat OpenShift GitOps version: v1.14.1}

Logs

Argocd Server log form request GET https://argocd.server/api/v1/repositories/https%253A%252F%252Fhelm.server%252Fsome%252Fpath%252F/helmcharts:

time="XXX-XX-XXTXX:XX:XXZ" level=info msg="received unary call /repository.RepositoryService/GetHelmCharts" grpc.method=GetHelmCharts grpc.request.claims="{\"iat\":XXXXXXXXXX,\"iss\":\"argocd\",\"jti\":\"my-token-name\",\"nbf\":XXXXXXXXXX,\"sub\":\"proj:myproject:myrole\"}" grpc.request.content="repo:\"https://helm.server/some/path/\" " grpc.service=repository.RepositoryService grpc.start_time="XXXX-XX-XXTXX:XX:XXZ" span.kind=server system=grpc
time="XXXX-XX-XXTXX:XX:XXZ" level=warning msg="Found multiple credentials for repoURL: https://helm.server/some/path/"
time="XXXX-XX-XXTXX:XX:XXZ" level=warning msg="Found multiple credentials for repoURL: https://helm.server/some/path/"
time="XXXX-XX-XXTXX:XX:XXZ" level=warning msg="finished unary call with code PermissionDenied" error="rpc error: code = PermissionDenied desc = permission denied: repositories, get, https://helm.server/some/path/, sub: proj:myproject:myrole, iat: XXXX-XX-XXTXX:XX:XXZ" grpc.code=PermissionDenied grpc.method=GetHelmCharts grpc.service=repository.RepositoryService grpc.start_time="XXXX-XX-XXTXX:XX:XXZ" grpc.time_ms=1.13 span.kind=server system=grpc

Argocd Server log form request with project name prefixed:

time="XXX-XX-XXTXX:XX:XXZ" level=warning msg="Found multiple credentials for repoURL: myproject/https://helm.server/some/path/"
time="XXX-XX-XXTXX:XX:XXZ" level=warning msg="Found multiple credentials for repoURL: myproject/https://helm.server/some/path/"
time="XXX-XX-XXTXX:XX:XXZ" level=error msg="finished unary call with code Unknown" error="rpc error: code = Unknown desc = Get \"myproject/https://helm.server/some/path/index.yaml\": unsupported protocol scheme \"\"" grpc.code=Unknown grpc.method=GetHelmCharts grpc.service=repository.RepositoryService grpc.start_time="XXX-XX-XXTXX:XX:XXZ" grpc.time_ms=17.462 span.kind=server system=grpc
carlbalmer commented 1 week ago

OK, I found the cause! This issue was an intended result of #18388, the proposal explains:

If there are multiple repositories with the same URL and assuming the user is allowed to access them, then setting a project parameter would be required, since there would otherwise be no way to determine which of the credentials a user wants to access. This is not a breaking change since this adds functionality which has previously not existed.

Adding the appProject query parameter solved the issue for us.

The use of project-scoped JWT tokens may be the reason that this was a breaking change for us.