Add gcloud_service auth option for Gerrit driver

A user running Zuul in the Google cloud may want to use service
accounts to authenticate to Gerrit.  Notably, the Gerrit project
itself operates in this manner.  Therefore, add support for
using the default service account to access Gerrit.

It is also very likely that a user may not want to expose a
service account with Gerrit credentials to the executors.
Therefore, we can not assume that they will have access to the
same service account.  Therefore if the gloud_service auth type
is specified for a Gerrit connection, we will only use anonymous
HTTP access for the git repositories.

This restriction is not ideal, and may be able to be removed later
if we find an out-of-band method of communicating a service account
token to the executor in a manner that would not make it available
to jobs running on it.

Change-Id: I02bc3219018278d517bc3ae6f1ac0be22ef7c0ed
This commit is contained in:
James E. Blair
2020-01-30 08:09:00 -08:00
parent a7a9c8b38f
commit b5a4d44645
3 changed files with 89 additions and 5 deletions
+9
View File
@@ -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
+66
View File
@@ -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
+14 -5
View File
@@ -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.