<p>I try to create roles in an automated way in Google Kubernetes (GKE).</p>

<p>For that, I use the python client library, but I don't want to have any dependency to kubectl and kubeconfig, or gcloud,</p>

<p>I use a service account (with a json key file from GCP) which has the permissions to create roles in namespaces (it is a cluster admin). When I use the access token given by this command :</p>

<pre><code>gcloud auth activate-service-account --key-file=credentials.json 
gcloud auth print-access-token
</code></pre>

<p>It works.</p>

<p>But when I try to generate the token by myself, I can create namespaces and other standard resources, but I have this error when it comes to roles :</p>

<pre><code>E           kubernetes.client.rest.ApiException: (403)
E           Reason: Forbidden
E           HTTP response headers: HTTPHeaderDict({'Audit-Id': 'b89b0fc2-9350-456e-9eca-730e7ad2cea1', 'Content-Type': 'application/json', 'Date': 'Tue, 26 Feb 2019 20:35:20 GMT', 'Content-Length': '1346'})
E           HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"roles.rbac.authorization.k8s.io \"developers\" is forbidden: attempt to grant extra privileges: [{[*] [apps] [statefulsets] [] []} {[*] [apps] [deployments] [] []} {[*] [autoscaling] [horizontalpodautoscalers] [] []} {[*] [] [pods] [] []} {[*] [] [pods/log] [] []} {[*] [] [pods/portforward] [] []} {[*] [] [serviceaccounts] [] []} {[*] [] [containers] [] []} {[*] [] [services] [] []} {[*] [] [secrets] [] []} {[*] [] [configmaps] [] []} {[*] [extensions] [ingressroutes] [] []} {[*] [networking.istio.io] [virtualservices] [] []}] user=\u0026{100701357824788592239  [system:authenticated] map[user-assertion.cloud.google.com:[AKUJVp+KNvF6jw9II+AjCdqjbC0vz[...]hzgs0JWXOyk7oxWHkaXQ==]]} ownerrules=[{[create] [authorization.k8s.io] [selfsubjectaccessreviews selfsubjectrulesreviews] [] []} {[get] [] [] [] [/api /api/* /apis /apis/* /healthz /openapi /openapi/* /swagger-2.0.0.pb-v1 /swagger.json /swaggerapi /swaggerapi/* /version /version/]}] ruleResolutionErrors=[]","reason":"Forbidden","details":{"name":"developers","group":"rbac.authorization.k8s.io","kind":"roles"},"code":403}
</code></pre>

<p>I'm using the same service account, so I guess gcloud is doing something more than my script.</p>

<p>Here the python code I use to generate the token :</p>

<pre><code>def _get_token(self) -&gt; str:
    # See documentation here
    # https://developers.google.com/identity/protocols/OAuth2ServiceAccount
    epoch_time = int(time.time())
    # Generate a claim from the service account file.
    claim = {
        "iss": self._service_account_key["client_email"],
        "scope": "https://www.googleapis.com/auth/cloud-platform",
        "aud": "https://www.googleapis.com/oauth2/v4/token",
        "exp": epoch_time + 3600,
        "iat": epoch_time
    }
    # Sign claim with JWT.
    assertion = jwt.encode(
        claim,
        self._service_account_key["private_key"],
        algorithm='RS256'
    ).decode()
    # Create payload for API.
    data = urlencode({
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": assertion
    })
    # Request the access token.
    result = requests.post(
        url="https://www.googleapis.com/oauth2/v4/token",
        headers={
            "Content-Type": "application/x-www-form-urlencoded"
        },
        data=data
    )
    result.raise_for_status()
    return json.loads(result.text)["access_token"]

def _get_api_client(self) -&gt; client.ApiClient:
    configuration = client.Configuration()
    configuration.host = self._api_url
    configuration.verify_ssl = self._tls_verify
    configuration.api_key = {
        "authorization": f"Bearer {self._get_token()}"
    }
    return client.ApiClient(configuration)
</code></pre>

<p>And the function to create the role (which generates the 403 error):</p>

<pre><code>def _create_role(self, namespace: str, body: str):
    api_client = self._get_api_client()
    rbac = client.RbacAuthorizationV1Api(api_client)
    rbac.create_namespaced_role(
        namespace,
        body
    )
</code></pre>

<p>If I short-circuit the _get_token method with the token extracted from gcloud, it works.</p>

<p>I guess it has something to do with the way I create my token (missing scope ?), but I don't find any documentation about it.</p>

<p><strong>ANSWER :</strong></p>

<p>Adding a scope does the job ! Thanks a lot :</p>

<pre><code># Generate a claim from the service account file.
        claim = {
            "iss": self._service_account_key["client_email"],
            "scope": " ".join([
                "https://www.googleapis.com/auth/cloud-platform",
                "https://www.googleapis.com/auth/userinfo.email"
            ]),
            "aud": "https://www.googleapis.com/oauth2/v4/token",
            "exp": epoch_time + 3600,
            "iat": epoch_time
        }
</code></pre>

<p>So if you look at the code <a href="https://github.com/google-cloud-sdk/google-cloud-sdk/blob/af9de150b5107cd0dc66cb1cf3de417f06d56691/lib/surface/auth/application_default/print_access_token.py#L67" rel="nofollow noreferrer">here</a> for <code>print-access-token</code> you can see that the access token is generally printed without a scope.  You see:</p>

<pre><code>try:
  creds = client.GoogleCredentials.get_application_default()
except client.ApplicationDefaultCredentialsError as e:
  log.debug(e, exc_info=True)
  raise c_exc.ToolException(str(e))

if creds.create_scoped_required():
   ...
</code></pre>

<p>and then on this <a href="https://github.com/google-cloud-sdk/google-cloud-sdk/blob/af9de150b5107cd0dc66cb1cf3de417f06d56691/lib/third_party/oauth2client/client.py#L1163" rel="nofollow noreferrer">file</a> you see:</p>

<pre><code>def create_scoped_required(self):
    """Whether this Credentials object is scopeless.
    create_scoped(scopes) method needs to be called in order to create
    a Credentials object for API calls.
    """
    return False
</code></pre>

<p>Apparently, in your code, you are getting the token with the <code>https://www.googleapis.com/auth/cloud-platform</code> scope.  You could try removing it or try with the <a href="https://github.com/google-cloud-sdk/google-cloud-sdk/blob/5514f695e0f3068d72f6ee7ee0de57e89aa259d1/lib/googlecloudsdk/api_lib/auth/util.py#L42" rel="nofollow noreferrer">USER_EMAIL_SCOPE</a> since you are specifying: <code>"iss": self._service_account_key["client_email"]</code>.</p>

<p>You can always check what <code>gcloud auth activate-service-account --key-file=credentials.json</code> stores under <code>~/.config</code>. So you know what <code>gcloud auth print-access-token</code> uses. Note that as per <a href="https://github.com/google-cloud-sdk/google-cloud-sdk/blob/5514f695e0f3068d72f6ee7ee0de57e89aa259d1/lib/googlecloudsdk/core/credentials/store.py#L415" rel="nofollow noreferrer">this</a> and <a href="https://github.com/google-cloud-sdk/google-cloud-sdk/blob/5514f695e0f3068d72f6ee7ee0de57e89aa259d1/lib/googlecloudsdk/core/credentials/creds.py#L311" rel="nofollow noreferrer">this</a> it looks like the store is in <a href="https://www.sqlite.org/index.html" rel="nofollow noreferrer">sqlite</a> format.</p>
