diff --git a/doc/source/reference/drivers/gerrit.rst b/doc/source/reference/drivers/gerrit.rst index cc924eb4d6..dc1ecaa280 100644 --- a/doc/source/reference/drivers/gerrit.rst +++ b/doc/source/reference/drivers/gerrit.rst @@ -122,6 +122,15 @@ The supported options in ``zuul.conf`` connections are: Zuul will submit a username and password to a form in order to authenticate. + .. value:: gcloud_service + + Only valid when running in Google Cloud. This will use the + default service account to authenticate to Gerrit. Note that + this will only be used for interacting with the Gerrit API; + anonymous HTTP access will be used to access the git + repositories, therefore private repos or draft changes will + not be available. + .. attr:: verify_ssl :default: true diff --git a/zuul/driver/gerrit/gcloudauth.py b/zuul/driver/gerrit/gcloudauth.py new file mode 100644 index 0000000000..addb471fac --- /dev/null +++ b/zuul/driver/gerrit/gcloudauth.py @@ -0,0 +1,66 @@ +# Copyright 2012 Google Inc. +# Copyright 2019 Red Hat, 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. + +import logging +import requests +import threading +import time + +TOKEN_URL = ('http://metadata.google.internal/computeMetadata/' + 'v1/instance/service-accounts/default/token') +REFRESH = 25 +RETRY_INTERVAL = 5 + + +class GCloudAuth(requests.auth.AuthBase): + log = logging.getLogger('zuul.GerritConnection') + + def __init__(self, user, password): + self.token = None + self.expires = 0 + try: + self.getToken() + except Exception: + self.log.exception("Error updating token:") + self.update_thread = threading.Thread(target=self.update) + self.update_thread.daemon = True + self.update_thread.start() + + def update(self): + while True: + try: + self._update() + except Exception: + self.log.exception("Error updating token:") + time.sleep(5) + + def _update(self): + now = time.time() + expires = self.expires - REFRESH + expires = max(expires, now + RETRY_INTERVAL) + while now < expires: + time.sleep(expires - now) + now = time.time() + self.getToken() + + def getToken(self): + r = requests.get(TOKEN_URL, headers={'Metadata-Flavor': 'Google'}) + data = r.json() + self.token = data['access_token'] + self.expires = time.time() + data['expires_in'] + + def __call__(self, request): + request.prepare_cookies({'o': self.token}) + return request diff --git a/zuul/driver/gerrit/gerritconnection.py b/zuul/driver/gerrit/gerritconnection.py index 7dea617ea8..5e58991d45 100644 --- a/zuul/driver/gerrit/gerritconnection.py +++ b/zuul/driver/gerrit/gerritconnection.py @@ -37,6 +37,7 @@ from uuid import uuid4 from zuul import version as zuul_version from zuul.connection import BaseConnection from zuul.driver.gerrit.auth import FormAuth +from zuul.driver.gerrit.gcloudauth import GCloudAuth from zuul.driver.gerrit.gerritmodel import GerritChange, GerritTriggerEvent from zuul.lib.logutil import get_annotated_logger from zuul.model import Ref, Tag, Branch, Project @@ -474,8 +475,9 @@ class GerritConnection(BaseConnection): self.session = None self.password = self.connection_config.get('password', None) - if self.password: - self.auth_type = self.connection_config.get('auth_type', None) + self.auth_type = self.connection_config.get('auth_type', None) + self.anonymous_git = False + if self.password or self.auth_type == 'gcloud_service': self.verify_ssl = self.connection_config.get('verify_ssl', True) if self.verify_ssl not in ['true', 'True', '1', 1, 'TRUE']: self.verify_ssl = False @@ -487,10 +489,15 @@ class GerritConnection(BaseConnection): authclass = requests.auth.HTTPDigestAuth elif self.auth_type == 'form': authclass = FormAuth + elif self.auth_type == 'gcloud_service': + authclass = GCloudAuth + # The executors in google cloud may not have access + # to the gerrit account credentials, so just use + # anonymous http access for git + self.anonymous_git = True else: authclass = requests.auth.HTTPBasicAuth - self.auth = authclass( - self.user, self.password) + self.auth = authclass(self.user, self.password) def setWatchedCheckers(self, checkers_to_watch): self.log.debug("Setting watched checkers to %s", checkers_to_watch) @@ -1291,7 +1298,9 @@ class GerritConnection(BaseConnection): return ret def getGitUrl(self, project: Project) -> str: - if self.session: + if self.anonymous_git: + url = ('%s/%s' % (self.baseurl, project.name)) + elif self.session: baseurl = list(urllib.parse.urlparse(self.baseurl)) # Make sure we escape '/' symbols, otherwise git's url # parser will think the username is a hostname.