diff -rNu buildbot/secrets.orig/providers/__init__.py buildbot/secrets/providers/__init__.py --- buildbot/secrets.orig/providers/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ buildbot/secrets/providers/__init__.py 2017-03-28 21:52:03.947803965 +0200 @@ -0,0 +1,14 @@ +# This file is part of Buildbot. Buildbot is free software: you can +# redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright Buildbot Team Members diff -rNu buildbot/secrets.orig/providers/base.py buildbot/secrets/providers/base.py --- buildbot/secrets.orig/providers/base.py 1970-01-01 01:00:00.000000000 +0100 +++ buildbot/secrets/providers/base.py 2017-03-28 21:52:03.947803965 +0200 @@ -0,0 +1,35 @@ +# This file is part of Buildbot. Buildbot is free software: you can +# redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright Buildbot Team Members +""" +secret provider interface +""" +from __future__ import absolute_import +from __future__ import print_function + +import abc + +from buildbot.util.service import BuildbotService + + +class SecretProviderBase(BuildbotService): + """ + Secret provider base + """ + + @abc.abstractmethod + def get(self, *args, **kwargs): + """ + this should be an abstract method + """ diff -rNu buildbot/secrets.orig/providers/file.py buildbot/secrets/providers/file.py --- buildbot/secrets.orig/providers/file.py 1970-01-01 01:00:00.000000000 +0100 +++ buildbot/secrets/providers/file.py 2017-03-28 21:52:03.947803965 +0200 @@ -0,0 +1,82 @@ +# This file is part of Buildbot. Buildbot is free software: you can +# redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright Buildbot Team Members +""" +file based provider +""" +from __future__ import absolute_import +from __future__ import print_function + +import os +import stat + +from buildbot import config +from buildbot.secrets.providers.base import SecretProviderBase + + +class SecretInAFile(SecretProviderBase): + """ + secret is stored in a separate file under the given directory name + """ + name = "SecretInAFile" + + def checkFileIsReadOnly(self, dirname, secretfile): + filepath = os.path.join(dirname, secretfile) + obs_stat = stat.S_IMODE(os.stat(filepath).st_mode) + if (obs_stat & 0o77) != 0 and os.name == "posix": + config.error("Permissions %s on file %s are too open." + " It is required that your secret files are NOT" + " accessible by others!" % (oct(obs_stat), + secretfile)) + + def checkSecretDirectoryIsAvailableAndReadable(self, dirname, suffixes): + if not os.access(dirname, os.F_OK): + config.error("directory %s does not exists" % dirname) + for secretfile in os.listdir(dirname): + for suffix in suffixes: + if secretfile.endswith(suffix): + self.checkFileIsReadOnly(dirname, secretfile) + + def loadSecrets(self, dirname, suffixes): + secrets = {} + for secretfile in os.listdir(dirname): + secretvalue = None + for suffix in suffixes: + if secretfile.endswith(suffix): + with open(os.path.join(dirname, secretfile)) as source: + secretvalue = source.read() + if suffix: + secretfile = secretfile[:-len(suffix)] + secrets[secretfile] = secretvalue + return secrets + + def checkConfig(self, dirname, suffixes=None): + self._dirname = dirname + if suffixes is None: + suffixes = [""] + self.checkSecretDirectoryIsAvailableAndReadable(dirname, + suffixes=suffixes) + + def reconfigService(self, dirname, suffixes=None): + self._dirname = dirname + self.secrets = {} + if suffixes is None: + suffixes = [""] + self.secrets = self.loadSecrets(self._dirname, suffixes=suffixes) + + def get(self, entry): + """ + get the value from the file identified by 'entry' + """ + return self.secrets.get(entry) diff -rNu buildbot/secrets.orig/providers/vault.py buildbot/secrets/providers/vault.py --- buildbot/secrets.orig/providers/vault.py 1970-01-01 01:00:00.000000000 +0100 +++ buildbot/secrets/providers/vault.py 2017-03-28 21:52:03.947803965 +0200 @@ -0,0 +1,67 @@ +# This file is part of Buildbot. Buildbot is free software: you can +# redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright Buildbot Team Members +""" +vault based providers +""" + +from __future__ import absolute_import +from __future__ import print_function + +from twisted.internet import defer + +from buildbot import config +from buildbot.secrets.providers.base import SecretProviderBase +from buildbot.util import httpclientservice + + +class HashiCorpVaultSecretProvider(SecretProviderBase): + """ + basic provider where each secret is stored in Vault + """ + + name = 'SecretInVault' + + def checkConfig(self, vaultServer=None, vaultToken=None, secretsmount=None): + if not isinstance(vaultServer, str): + config.error("vaultServer must be a string while it is %s" % (type(vaultServer,))) + if not isinstance(vaultToken, str): + config.error("vaultToken must be a string while it is %s" % (type(vaultToken,))) + + @defer.inlineCallbacks + def reconfigService(self, vaultServer=None, vaultToken=None, secretsmount=None): + if secretsmount is None: + self.secretsmount = "secret" + else: + self.secretsmount = secretsmount + self.vaultServer = vaultServer + self.vaultToken = vaultToken + if vaultServer.endswith('/'): + vaultServer = vaultServer[:-1] + self._http = yield httpclientservice.HTTPClientService.getService( + self.master, self.vaultServer, headers={'X-Vault-Token': self.vaultToken}) + + @defer.inlineCallbacks + def get(self, entry): + """ + get the value from vault secret backend + """ + path = self.secretsmount + '/' + entry + proj = yield self._http.get('/v1/{0}'.format(path)) + code = yield proj.code + if code != 200: + raise KeyError("The key %s does not exist in Vault provider: request" + " return code:%d." % (entry, code)) + json = yield proj.json() + defer.returnValue(json.get(u'data', {}).get('value'))