1# Licensed under the Apache License, Version 2.0 (the "License"); you may 2# not use this file except in compliance with the License. You may obtain 3# a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10# License for the specific language governing permissions and limitations 11# under the License. 12r""" 13Remote File 14----------- 15 16The **remote_file** backend driver is the first driver implemented by 17oslo.config. It extends the previous limit of only accessing local files 18to a new scenario where it is possible to access configuration data over 19the network. The **remote_file** driver is based on the **requests** module 20and is capable of accessing remote files through **HTTP** or **HTTPS**. 21 22To definition of a remote_file configuration data source can be as minimal as:: 23 24 [DEFAULT] 25 config_source = external_config_group 26 27 [external_config_group] 28 driver = remote_file 29 uri = http://mydomain.com/path/to/config/data.conf 30 31Or as complete as:: 32 33 [DEFAULT] 34 config_source = external_config_group 35 36 [external_config_group] 37 driver = remote_file 38 uri = https://mydomain.com/path/to/config/data.conf 39 ca_path = /path/to/server/ca.pem 40 client_key = /path/to/my/key.pem 41 client_cert = /path/to/my/cert.pem 42 43On the following sessions, you can find more information about this driver's 44classes and its options. 45 46The Driver Class 47================ 48 49.. autoclass:: URIConfigurationSourceDriver 50 51The Configuration Source Class 52============================== 53 54.. autoclass:: URIConfigurationSource 55 56""" 57 58import requests 59import tempfile 60 61from oslo_config import cfg 62from oslo_config import sources 63 64 65class URIConfigurationSourceDriver(sources.ConfigurationSourceDriver): 66 """A backend driver for remote files served through http[s]. 67 68 Required options: 69 - uri: URI containing the file location. 70 71 Non-required options: 72 - ca_path: The path to a CA_BUNDLE file or directory with 73 certificates of trusted CAs. 74 75 - client_cert: Client side certificate, as a single file path 76 containing either the certificate only or the 77 private key and the certificate. 78 79 - client_key: Client side private key, in case client_cert is 80 specified but does not includes the private key. 81 """ 82 83 _uri_driver_opts = [ 84 cfg.URIOpt( 85 'uri', 86 schemes=['http', 'https'], 87 required=True, 88 sample_default='https://example.com/my-configuration.ini', 89 help=('Required option with the URI of the ' 90 'extra configuration file\'s location.'), 91 ), 92 cfg.StrOpt( 93 'ca_path', 94 sample_default='/usr/local/etc/ca-certificates', 95 help=('The path to a CA_BUNDLE file or directory ' 96 'with certificates of trusted CAs.'), 97 ), 98 cfg.StrOpt( 99 'client_cert', 100 sample_default='/usr/local/etc/ca-certificates/service-client-keystore', 101 help=('Client side certificate, as a single file path ' 102 'containing either the certificate only or the ' 103 'private key and the certificate.'), 104 ), 105 cfg.StrOpt( 106 'client_key', 107 help=('Client side private key, in case client_cert is ' 108 'specified but does not includes the private key.'), 109 ), 110 ] 111 112 def list_options_for_discovery(self): 113 return self._uri_driver_opts 114 115 def open_source_from_opt_group(self, conf, group_name): 116 conf.register_opts(self._uri_driver_opts, group_name) 117 118 return URIConfigurationSource( 119 conf[group_name].uri, 120 conf[group_name].ca_path, 121 conf[group_name].client_cert, 122 conf[group_name].client_key) 123 124 125class URIConfigurationSource(sources.ConfigurationSource): 126 """A configuration source for remote files served through http[s]. 127 128 :param uri: The Uniform Resource Identifier of the configuration to be 129 retrieved. 130 131 :param ca_path: The path to a CA_BUNDLE file or directory with 132 certificates of trusted CAs. 133 134 :param client_cert: Client side certificate, as a single file path 135 containing either the certificate only or the 136 private key and the certificate. 137 138 :param client_key: Client side private key, in case client_cert is 139 specified but does not includes the private key. 140 """ 141 142 def __init__(self, uri, ca_path=None, client_cert=None, client_key=None): 143 self._uri = uri 144 self._namespace = cfg._Namespace(cfg.ConfigOpts()) 145 146 data = self._fetch_uri(uri, ca_path, client_cert, client_key) 147 148 with tempfile.NamedTemporaryFile() as tmpfile: 149 tmpfile.write(data.encode("utf-8")) 150 tmpfile.flush() 151 152 cfg.ConfigParser._parse_file(tmpfile.name, self._namespace) 153 154 def _fetch_uri(self, uri, ca_path, client_cert, client_key): 155 verify = ca_path if ca_path else True 156 cert = (client_cert, client_key) if client_cert and client_key else \ 157 client_cert 158 159 with requests.get(uri, verify=verify, cert=cert) as response: 160 response.raise_for_status() # raises only in case of HTTPError 161 162 return response.text 163 164 def get(self, group_name, option_name, opt): 165 try: 166 return self._namespace._get_value( 167 [(group_name, option_name)], 168 multi=opt.multi) 169 except KeyError: 170 return (sources._NoValue, None) 171