Skip to content

Add delegate_credentials #299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Nov 9, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ also provides integration with several HTTP libraries.
- Support for signing and verifying :mod:`JWTs <google.auth.jwt>`.
- Support for verifying and decoding :mod:`ID Tokens <google.oauth2.id_token>`.
- Support for Google :mod:`Service Account credentials <google.oauth2.service_account>`.
- Support for Google :mod:`Impersonated Credentials <google.auth.impersonated_credentials>`.
- Support for :mod:`Google Compute Engine credentials <google.auth.compute_engine>`.
- Support for :mod:`Google App Engine standard credentials <google.auth.app_engine>`.
- Support for various transports, including
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/google.auth.impersonated_credentials.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
google.auth.impersonated\_credentials module
============================================

.. automodule:: google.auth.impersonated_credentials
:members:
:inherited-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/reference/google.auth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ Submodules
google.auth.environment_vars
google.auth.exceptions
google.auth.iam
google.auth.impersonated_credentials
google.auth.jwt

31 changes: 31 additions & 0 deletions docs/user-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,37 @@ You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth
.. _requests-oauthlib:
https://requests-oauthlib.readthedocs.io/en/latest/

Impersonated credentials
++++++++++++++++++++++++

Impersonated Credentials allows one set of credentials issued to a user or service account
to impersonate another. The target service account must grant the orginating credential
principal the "Service Account Token Creator" IAM role::

from google.auth.impersonated_credentials import ImpersonatedCredentials

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/path/to/svc_account.json'
scopes = ['https://www.googleapis.com/auth/devstorage.read_only']

root_credentials, project = google.auth.default(scopes=scopes)
client = storage.Client(credentials=root_credentials)

impersonated_credentials = ImpersonatedCredentials(
root_credentials = root_credentials,

This comment was marked as spam.

This comment was marked as spam.

principal='impersonated-account@_project_.iam.gserviceaccount.com',
new_scopes = scopes,
delegates=[],
lifetime=500)
client = storage.Client(credentials=impersonated_credentials)
buckets = client.list_buckets(project='your_project')
for bkt in buckets:
print bkt.name


In the example above `root_credentials` does not have direct access to list buckets
in the target project. Using `ImpersonatedCredentials` will allow the root_credentials
to assume the identity of a principal that does have access

Making authenticated requests
-----------------------------

Expand Down
190 changes: 190 additions & 0 deletions google/auth/impersonated_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Copyright 2018 Google Inc.
#
# 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.

"""Google Cloud Impersonated credentials.

This module provides authentication for applications where local credentials
impersonates a remote service account using `IAM Credentials API`_.

This class can be used to impersonate a service account as long as the original
Credential object has the "Service Account Token Creator" role on the target
service account.

.. _IAM Credentials API:
https://cloud.google.com/iam/credentials/reference/rest/
"""

import copy
from datetime import datetime
import json

from google.auth import _helpers
from google.auth import credentials
from google.auth import exceptions

_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds

_IAM_SCOPE = ['https://www.googleapis.com/auth/iam']

_IAM_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/projects/-' +
'/serviceAccounts/{}:generateAccessToken')

_REFRESH_ERROR = 'Unable to acquire impersonated credentials '
_LIFETIME_ERROR = 'Credentials with lifetime set cannot be renewed'


class ImpersonatedCredentials(credentials.Credentials):
"""This module defines impersonated credentials which are essentially
impersonated identities.

Impersonated Credentials allows credentials issued to a user or
service account to impersonate another. The target service account must
grant the orginating credential principal the
`Service Account Token Creator`_ IAM role:

For more information about Token Creator IAM role and
IAMCredentials API, see
`Creating Short-Lived Service Account Credentials`_.

.. _Service Account Token Creator:
https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role

.. _Creating Short-Lived Service Account Credentials:
https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials

Usage:

First grant root_credentials the `Service Account Token Creator`
role on the account to impersonate. In this example, the
service account represented by svc_account.json has the
token creator role on
`impersonated-account@_project_.iam.gserviceaccount.com`.

Initialze a root credential which does not have access to
list bucket::

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'svc_account.json'
scopes = ['https://www.googleapis.com/auth/devstorage.read_only']

root_credentials, project = google.auth.default(scopes=scopes)

Now use the root credentials to acquire credentials to impersonate
another service account::

impersonated_credentials = ImpersonatedCredentials(
root_credentials = root_credentials,
principal='impersonated-account@_project_.iam.gserviceaccount.com',
new_scopes = scopes,
delegates=[],
lifetime=500)

Resource access is granted::

client = storage.Client(credentials=impersonated_credentials)
buckets = client.list_buckets(project='your_project')
for bkt in buckets:
print bkt.name
"""

def __init__(self, root_credentials, principal,
new_scopes, delegates=None,
lifetime=None):
"""
Args:
root_credentials (google.auth.Credentials): The root credential

This comment was marked as spam.

used as to acquire the impersonated credentials.
principal (str): The service account to impersonatge.

This comment was marked as spam.

This comment was marked as spam.

new_scopes (Sequence[str]): Scopes to request during the
authorization grant.
delegates (Sequence[str]): The chained list of delegates required
to grant the final access_token.
lifetime (int): Number of seconds the delegated credential should
be valid for (upto 3600). If set, the credentials will
**not** get refreshed after expiration. If not set, the
credentials will be refreshed every 3600s.
"""

super(credentials.Credentials, self).__init__()

self._root_credentials = copy.copy(root_credentials)
self._root_credentials._scopes = _IAM_SCOPE
self._principal = principal
self._new_scopes = new_scopes
self._delegates = delegates
self._lifetime = lifetime
self.token = None
self.expiry = _helpers.utcnow()

@_helpers.copy_docstring(credentials.Credentials)
def refresh(self, request):
if (self.token is not None and self._lifetime is not None):
self.expiry = _helpers.utcnow()
raise exceptions.RefreshError(_LIFETIME_ERROR)
self._root_credentials.refresh(request)
self._updateToken(request)

@property
def expired(self):
return _helpers.utcnow() >= self.expiry

def _updateToken(self, req):
"""Updates credentials with a new access_token representing
the impersonated account.

Args:
req (google.auth.transport.requests.Request): Request object to use
for refreshing credentials.

Raises:
TransportError: Raised if there is an underlying HTTP connection
Error
DefaultCredentialsError: Raised if the impersonated credentials
are not available. Common reasons are
`iamcredentials.googleapis.com` is not enabled or the
`Service Account Token Creator` is not assigned
"""

lifetime = self._lifetime
if (self._lifetime is None):
lifetime = _DEFAULT_TOKEN_LIFETIME_SECS
body = {
"delegates": self._delegates,
"scope": self._new_scopes,
"lifetime": str(lifetime) + "s"
}

iam_endpoint = _IAM_ENDPOINT.format(self._principal)
try:
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + self._root_credentials.token
}
response = req(url=iam_endpoint,
method='POST',
headers=headers,
json=body)
if (response.status == 200):
token_response = json.loads(response.data.decode('utf-8'))
self.token = token_response['accessToken']
self.expiry = datetime.strptime(token_response['expireTime'],
'%Y-%m-%dT%H:%M:%SZ')
else:
raise exceptions.DefaultCredentialsError(_REFRESH_ERROR +
self._principal)
except (ValueError, KeyError, TypeError):
raise exceptions.DefaultCredentialsError(_REFRESH_ERROR +
self._principal)
except (exceptions.TransportError):
raise exceptions.TransportError(_REFRESH_ERROR +
self._principal)
Loading