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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user