blob: 72429e990329016bf57911ee768ffa6d8cb8531e [file] [log] [blame]
James E. Blairc49e5e72017-03-16 14:56:32 -07001#!/usr/bin/env python
2
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import argparse
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020016import base64
James E. Blairc49e5e72017-03-16 14:56:32 -070017import os
18import subprocess
19import sys
20import tempfile
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020021
22# we to import Request and urlopen differently for python 2 and 3
23try:
24 from urllib.request import Request
25 from urllib.request import urlopen
26except ImportError:
27 from urllib2 import Request
28 from urllib2 import urlopen
James E. Blairc49e5e72017-03-16 14:56:32 -070029
30DESCRIPTION = """Encrypt a secret for Zuul.
31
32This program fetches a project-specific public key from a Zuul server and
33uses that to encrypt a secret. The only pre-requisite is an installed
34OpenSSL binary.
35"""
36
37
38def main():
39 parser = argparse.ArgumentParser(description=DESCRIPTION)
40 parser.add_argument('url',
41 help="The base URL of the zuul server and tenant. "
42 "E.g., https://zuul.example.com/tenant-name")
43 # TODO(jeblair,mordred): When projects have canonical names, use that here.
44 # TODO(jeblair): Throw a fit if SSL is not used.
45 parser.add_argument('source',
46 help="The Zuul source of the project.")
47 parser.add_argument('project',
48 help="The name of the project.")
49 parser.add_argument('--infile',
50 default=None,
51 help="A filename whose contents will be encrypted. "
52 "If not supplied, the value will be read from "
53 "standard input.")
54 parser.add_argument('--outfile',
55 default=None,
56 help="A filename to which the encrypted value will be "
57 "written. If not supplied, the value will be written "
58 "to standard output.")
59 args = parser.parse_args()
60
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020061 req = Request("%s/keys/%s/%s.pub" % (
James E. Blairc49e5e72017-03-16 14:56:32 -070062 args.url, args.source, args.project))
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020063 pubkey = urlopen(req)
James E. Blairc49e5e72017-03-16 14:56:32 -070064
65 if args.infile:
66 with open(args.infile) as f:
67 plaintext = f.read()
68 else:
69 plaintext = sys.stdin.read()
70
71 pubkey_file = tempfile.NamedTemporaryFile(delete=False)
72 try:
73 pubkey_file.write(pubkey.read())
74 pubkey_file.close()
75
76 p = subprocess.Popen(['openssl', 'rsautl', '-encrypt',
77 '-oaep', '-pubin', '-inkey',
78 pubkey_file.name],
79 stdin=subprocess.PIPE,
80 stdout=subprocess.PIPE)
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020081 (stdout, stderr) = p.communicate(plaintext.encode("utf-8"))
James E. Blairc49e5e72017-03-16 14:56:32 -070082 if p.returncode != 0:
83 raise Exception("Return code %s from openssl" % p.returncode)
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020084 ciphertext = base64.b64encode(stdout)
James E. Blairc49e5e72017-03-16 14:56:32 -070085 finally:
86 os.unlink(pubkey_file.name)
87
88 if args.outfile:
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020089 with open(args.outfile, "wb") as f:
James E. Blairc49e5e72017-03-16 14:56:32 -070090 f.write(ciphertext)
91 else:
Tobias Henkel39d6dcd2017-06-24 21:26:57 +020092 print(ciphertext.decode("utf-8"))
James E. Blairc49e5e72017-03-16 14:56:32 -070093
94
95if __name__ == '__main__':
96 main()