def test_parser(self): """ ``AcmeParser`` creates an endpoint with the specified ACME directory and directory store. """ directory = URL.fromText(u'https://example.com/acme') parser = _AcmeParser(u'prefix', directory) tempdir = self.useFixture(TempDir()).path temp_path = FilePath(tempdir) key_path = temp_path.child('client.key') reactor = object() self.assertThat( parser.parseStreamServer(reactor, tempdir, 'tcp', '443'), MatchesAll( IsInstance(AutoTLSEndpoint), MatchesStructure( reactor=Is(reactor), directory=Equals(directory), cert_store=MatchesAll( IsInstance(DirectoryStore), MatchesStructure( path=Equals(temp_path))), cert_mapping=MatchesAll( IsInstance(HostDirectoryMap), MatchesStructure( directoryPath=Equals(temp_path))), sub_endpoint=MatchesPredicate( IStreamServerEndpoint.providedBy, '%r is not a stream server endpoint')))) self.assertThat(key_path.isfile(), Equals(True)) key_data = key_path.getContent() parser.parseStreamServer(reactor, tempdir, 'tcp', '443'), self.assertThat(key_path.getContent(), Equals(key_data))
def _parse_fluentd_http(self, kind, args): return lambda reactor: FluentdDestination( # Construct the pool ourselves with the default of using # persistent connections to override Agent's default of not using # persistent connections. agent=Agent(reactor, pool=HTTPConnectionPool(reactor)), fluentd_url=URL.fromText(args), )
def _reconstitute(self): """ Reconstitute this L{URLPath} from all its given attributes. """ urltext = urlquote( urlparse.urlunsplit((self._scheme, self._netloc, self._path, self._query, self._fragment)), safe=_allascii ) self._url = _URL.fromText(urltext.encode("ascii").decode("ascii"))
def get_kubernetes_service(self, reactor): if self["k8s-service-account"]: return network_kubernetes( # XXX is this really the url to use? base_url=URL.fromText(self["kubernetes"].decode("ascii")), agent=authenticate_with_serviceaccount(reactor), ) return network_kubernetes_from_context(reactor, self["k8s-context"], self["k8s-config"])
def from_service_account(cls): from twisted.internet import reactor kubernetes = network_kubernetes( base_url=URL.fromText(u"https://kubernetes/"), agent=authenticate_with_serviceaccount(reactor), ) client = kubernetes.client() return cls(k8s=client)
def _url(self, *segments, **query): url = URL.fromText( self.endpoint.decode("utf-8"), ).child(*segments) for k, v in query.items(): url = url.add( k.decode("utf-8"), quote(v.encode("utf-8"), safe="").decode("ascii"), ) return url.asURI().asText().encode("ascii")
def gotRTM(response): url = URL.fromText(response[u'url']) if url.scheme != u'wss': raise RuntimeError(url) factory.setSessionParameters( response[u'url'], useragent=factory.useragent) self.me = response[u'self'] self.users = {u[u'id']: u for u in response[u'users']} self.channels = {c[u'id']: c for c in response[u'channels']} self.ims = {im[u'id']: im for im in response[u'ims']} print factory return self._makeEndpoint(url).connect(factory)
def setUp(self): super(EndpointTests, self).setUp() clock = Clock() clock.rightNow = ( datetime.now() - datetime(1970, 1, 1)).total_seconds() client = FakeClient(RSA_KEY_512, clock) self.endpoint = AutoTLSEndpoint( reactor=clock, directory=URL.fromText(u'https://example.com/'), client_creator=lambda reactor, directory: succeed(client), cert_store=MemoryStore(), cert_mapping={}, sub_endpoint=DummyEndpoint())
def create_session(endpoint, request=treq.request): """ Create a new `Session` instance. :param unicode endpoint: URI to the root of the Documint service. :param request: Callable for making requests. :type request: Callable mimicing the signature of `treq.request`. """ uri = URL.fromText(endpoint).child(u'sessions').child(u'') request = documint_request_factory(request) d = post_json(request, uri.asURI().asText().encode('utf-8')) d.addCallback(itemgetter(u'links')) d.addCallback(Session, request) return d
def _make_wormhole_claim(self, customer_email, customer_id, subscription_id, old_secrets): plan_identifier = u"foobar" reactor = Clock() provisioner = get_provisioner( reactor, URL.fromText(u"http://subscription-manager/"), partial(self._provision_subscription, old_secrets), ) server = MemoryWormholeServer() signup = get_wormhole_signup( reactor, provisioner, server, URL.fromText(u"ws://foo.invalid/"), FilePath(self.mktemp()), ) d = signup.signup( customer_email, customer_id, subscription_id, plan_identifier, ) return self.successResultOf(d)
def _addSlash(request): """ Add a trailing slash to C{request}'s URI. @param request: The incoming request to add the ending slash to. @type request: An object conforming to L{twisted.web.iweb.IRequest} @return: A URI with a trailing slash, with query and fragment preserved. @rtype: L{bytes} """ url = URL.fromText(request.uri.decode('ascii')) # Add an empty path segment at the end, so that it adds a trailing slash url = url.replace(path=list(url.path) + [u""]) return url.asText().encode('ascii')
def __init__(self, scheme=b'', netloc=b'localhost', path=b'', query=b'', fragment=b''): self.scheme = scheme or b'http' self.netloc = netloc self.path = path or b'/' self.query = query self.fragment = fragment urltext = urlquote(urlparse.urlunsplit( (self.scheme, self.netloc, self.path, self.query, self.fragment)), safe=_allascii) self._url = _URL.fromText(urltext.encode("ascii").decode("ascii"))
def authenticate_with_serviceaccount(reactor, **kw): """ Create an ``IAgent`` which can issue authenticated requests to a particular Kubernetes server using a service account token. :param reactor: The reactor with which to configure the resulting agent. :param bytes path: The location of the service account directory. The default should work fine for normal use within a container. :return IAgent: An agent which will authenticate itself to a particular Kubernetes server and which will verify that server or refuse to interact with it. """ config = KubeConfig.from_service_account(**kw) token = config.user["token"] base_url = URL.fromText(config.cluster["server"].decode("ascii")) ca_certs = pem.parse(config.cluster["certificate-authority"].bytes()) if not ca_certs: raise ValueError("No certificate authority certificate found.") ca_cert = ca_certs[0] try: # Validate the certificate so we have early failures for garbage data. ssl.Certificate.load(ca_cert.as_bytes(), FILETYPE_PEM) except OpenSSLError as e: raise ValueError( "Invalid certificate authority certificate found.", str(e), ) netloc = NetLocation(host=base_url.host, port=base_url.port) policy = ClientCertificatePolicyForHTTPS( credentials={}, trust_roots={ netloc: ca_cert, }, ) agent = HeaderInjectingAgent( _to_inject=Headers({u"authorization": [u"Bearer {}".format(token)]}), _agent=Agent(reactor, contextFactory=policy), ) return agent
def create_marathon_acme( storage_dir, acme_directory, acme_email, allow_multiple_certs, marathon_addrs, marathon_timeout, sse_timeout, mlb_addrs, group, reactor): """ Create a marathon-acme instance. :param storage_dir: Path to the storage directory for certificates and the client key. :param acme_directory: Address for the ACME directory to use. :param acme_email: Email address to use when registering with the ACME service. :param allow_multiple_certs: Whether to allow multiple certificates per app port. :param marathon_addr: Address for the Marathon instance to find app domains that require certificates. :param marathon_timeout: Amount of time in seconds to wait for response headers to be received from Marathon. :param sse_timeout: Amount of time in seconds to wait for some event data to be received from Marathon. :param mlb_addrs: List of addresses for marathon-lb instances to reload when a new certificate is issued. :param group: The marathon-lb group (``HAPROXY_GROUP``) to consider when finding app domains. :param reactor: The reactor to use. """ storage_path, certs_path = init_storage_dir(storage_dir) acme_url = URL.fromText(_to_unicode(acme_directory)) key = maybe_key(storage_path) return MarathonAcme( MarathonClient(marathon_addrs, timeout=marathon_timeout, sse_kwargs={'timeout': sse_timeout}, reactor=reactor), group, DirectoryStore(certs_path), MarathonLbClient(mlb_addrs, reactor=reactor), create_txacme_client_creator(reactor, acme_url, key), reactor, acme_email, allow_multiple_certs)
def fromString(klass, url): """ Make a L{URLPath} from a L{str} or L{unicode}. @param url: A L{str} representation of a URL. @type url: L{str} or L{unicode}. @return: a new L{URLPath} derived from the given string. @rtype: L{URLPath} """ if not isinstance(url, (str, unicode)): raise ValueError("'url' must be a str or unicode") if isinstance(url, bytes): # On Python 2, accepting 'str' (for compatibility) means we might # get 'bytes'. On py3, this will not work with bytes due to the # check above. return klass.fromBytes(url) return klass._fromURL(_URL.fromText(url))
def setUp(self): super(TestSignupModule, self).setUp() self.mockconfigdir = FilePath('./test_signup').child('TestSignupModule') make_dirs(self.mockconfigdir.path) self.SIGNUPSPATH = 'mock_signups.csv' self.CONFIGFILEPATH = 'init_test_config.json' self.EC2SECRETPATH = 'mock_ec2_secret' self.S3SECRETPATH = 'mock_s3_secret' self.MONITORPUBKEYPATH = 'MONITORKEYS.pub' self.MEMAIL = 'MEMAIL' self.MKEYINFO = 'MKEYINFO' self.MCUSTOMER_ID = u'cus_x14Charactersx' self.MSUBSCRIPTION_ID = u'sub_x14Characterx' self.MPLAN_ID = 'XX_consumer_iteration_#_GREEKLETTER#_2XXX-XX-XX' self.MENCODED_IDS = 'on2wex3yge2eg2dbojqwg5dfoj4a-mn2xgx3yge2eg2dbojqwg5dfojzxq' FilePath(self.SIGNUPSPATH).setContent('') FilePath(self.CONFIGFILEPATH).setContent(CONFIGFILEJSON) FilePath(self.EC2SECRETPATH).setContent(MOCKEC2SECRETCONTENTS) FilePath(self.S3SECRETPATH).setContent(MOCKS3SECRETCONTENTS) FilePath(self.MONITORPUBKEYPATH).setContent(MONITORPUBKEY) self.DEPLOYMENT_CONFIGURATION = model.DeploymentConfiguration( domain=u"s4.example.com", kubernetes_namespace=u"testing", subscription_manager_endpoint=URL.fromText(u"http://localhost/"), s3_access_key_id=ZEROPRODUCT["s3_access_key_id"], s3_secret_key=MOCKS3SECRETCONTENTS, introducer_image=u"tahoe-introducer", storageserver_image=u"tahoe-storageserver", ) self.SUBSCRIPTION = model.SubscriptionDetails( bucketname="lae-" + self.MENCODED_IDS, oldsecrets=old_secrets().example(), customer_email=self.MEMAIL, customer_pgpinfo=self.MKEYINFO, product_id=u"filler", customer_id=self.MCUSTOMER_ID, subscription_id=self.MSUBSCRIPTION_ID, introducer_port_number=12345, storage_port_number=12346, )
def get_things_done(): """ Here is where the service part is setup and action is done. """ responders = yield start_responders() store = MemoryStore() # We first validate the directory. account_key = _get_account_key() try: client = yield Client.from_url( reactor, URL.fromText(acme_url.decode('utf-8')), key=JWKRSA(key=account_key), alg=RS256, ) except Exception as error: print('\n\nFailed to connect to ACME directory. %s' % (error, )) yield reactor.stop() defer.returnValue(None) service = AcmeIssuingService( email='[email protected],[email protected]', cert_store=store, client=client, clock=reactor, responders=responders, panic=on_panic, ) # Start the service and wait for it to start. yield service.start() # Wait for the existing certificate from the storage to be available. yield service.when_certs_valid() # Request a SAN ... if passed via command line. yield service.issue_cert(','.join(requested_domains)) yield service.stopService() print('That was all the example.')
def _finish_convergence_service(k8s_client, options, subscription_client): k8s = KubeClient(k8s=k8s_client) access_key_id = FilePath( options["aws-access-key-id-path"]).getContent().strip() secret_access_key = FilePath( options["aws-secret-access-key-path"]).getContent().strip() aws = AWSServiceRegion(creds=AWSCredentials( access_key=access_key_id, secret_key=secret_access_key, )) Message.log( event=u"convergence-service:key-notification", key_id=access_key_id.decode("ascii"), secret_key_hash=sha256(secret_access_key).hexdigest().decode("ascii"), ) # XXX I get to leave a ton of fields empty because I happen to know # they're not used in this codepath. :/ Maybe this suggests something has # gone wrong ... config = DeploymentConfiguration( domain=options["domain"].decode("ascii"), kubernetes_namespace=options["kubernetes-namespace"].decode("ascii"), subscription_manager_endpoint=URL.fromText( options["endpoint"].decode("ascii")), s3_access_key_id=access_key_id.decode("ascii"), s3_secret_key=secret_access_key.decode("ascii"), introducer_image=options["introducer-image"].decode("ascii"), storageserver_image=options["storageserver-image"].decode("ascii"), log_gatherer_furl=None, stats_gatherer_furl=None, ) return TimerService( options["interval"], divert_errors_to_log(converge, u"subscription_converger"), config, subscription_client, k8s, aws, )
def _finish_convergence_service( k8s_client, options, subscription_client, reactor, ): k8s = KubeClient(k8s=k8s_client) access_key_id = FilePath(options["aws-access-key-id-path"]).getContent().strip() secret_access_key = FilePath(options["aws-secret-access-key-path"]).getContent().strip() aws = AWSServiceRegion(creds=AWSCredentials( access_key=access_key_id, secret_key=secret_access_key, )) Message.log( event=u"convergence-service:key-notification", key_id=access_key_id.decode("ascii"), secret_key_hash=sha256(secret_access_key).hexdigest().decode("ascii"), ) config = DeploymentConfiguration( domain=options["domain"].decode("ascii"), kubernetes_namespace=options["kubernetes-namespace"].decode("ascii"), subscription_manager_endpoint=URL.fromText(options["endpoint"].decode("ascii")), s3_access_key_id=access_key_id.decode("ascii"), s3_secret_key=secret_access_key.decode("ascii"), introducer_image=options["introducer-image"].decode("ascii"), storageserver_image=options["storageserver-image"].decode("ascii"), log_gatherer_furl=options["log-gatherer-furl"], stats_gatherer_furl=options["stats-gatherer-furl"], ) return _convergence_service( reactor, options["interval"], config, subscription_client, k8s, aws, )
def network_kubernetes_from_context(reactor, context, path=None): """ Create a new ``IKubernetes`` provider based on a kube config file. :param reactor: A Twisted reactor which will be used for I/O and scheduling. :param unicode context: The name of the kube config context from which to load configuration details. :param FilePath path: The location of the kube config file to use. :return IKubernetes: The Kubernetes service described by the named context. """ if path is None: path = FilePath(expanduser(u"~/.kube/config")) config = KubeConfig.from_file(path.path) context = config.contexts[context] cluster = config.clusters[context[u"cluster"]] user = config.users[context[u"user"]] base_url = URL.fromText(cluster[u"server"].decode("ascii")) [ca_cert] = parse(cluster[u"certificate-authority"].bytes()) client_chain = parse(user[u"client-certificate"].bytes()) [client_key] = parse(user[u"client-key"].bytes()) agent = authenticate_with_certificate_chain( reactor, base_url, client_chain, client_key, ca_cert, ) return network_kubernetes( base_url=base_url, agent=agent, )
def __init__(self, perfmon, server_name, server_address, server_port, username, password, verify, included_objects): self.perfmon = perfmon self.server_name = server_name self.server_address = server_address self.server_port = server_port self.username = username self.password = password self.verify = verify self.included_objects = included_objects self.authorization = base64.b64encode('{}:{}'.format(self.username, self.password).encode('utf-8')) self.headers = Headers({b'Content-Type': [b'text/xml; charset=utf-8'], b'Authorization': [b'Basic ' + self.authorization]}) self.agent = RateLimitAgent() if not self.verify: self.agent.contextFactory.good_domains.append(self.server_address.encode('utf-8')) self.url = URL.fromText('https://{server_address:}:{server_port:}/perfmonservice2/services/PerfmonService?wsdl'.format(server_address=server_address, server_port=server_port)) self.openSession()
def test_parser(self): """ ``AcmeParser`` creates an endpoint with the specified ACME directory and directory store. """ directory = URL.fromText(u'https://example.com/acme') parser = _AcmeParser(u'prefix', directory) tempdir = self.useFixture(TempDir()).path temp_path = FilePath(tempdir) key_path = temp_path.child('client.key') reactor = MemoryReactorClock() self.assertThat( parser.parseStreamServer(reactor, tempdir, 'tcp', '443', timeout=0), MatchesAll( IsInstance(AutoTLSEndpoint), MatchesStructure( reactor=Is(reactor), directory=Equals(directory), cert_store=MatchesAll( IsInstance(DirectoryStore), MatchesStructure(path=Equals(temp_path))), cert_mapping=MatchesAll( IsInstance(HostDirectoryMap), MatchesStructure(directoryPath=Equals(temp_path))), sub_endpoint=MatchesPredicate( IStreamServerEndpoint.providedBy, '%r is not a stream server endpoint')))) self.assertThat(key_path.isfile(), Equals(True)) key_data = key_path.getContent() # Multiple instances with certificates from the same local directory, # will serve the same certificates. parser.parseStreamServer(reactor, tempdir, 'tcp', '443', timeout=0) self.assertThat(key_path.getContent(), Equals(key_data)) # Check that reactor is clean. self.assertEquals(0, len(reactor.getDelayedCalls()))
def https_policy_from_config(config): """ Create an ``IPolicyForHTTPS`` which can authenticate a Kubernetes API server. :param KubeConfig config: A Kubernetes configuration containing an active context identifying a cluster. The resulting ``IPolicyForHTTPS`` will authenticate the API server for that cluster. :return IPolicyForHTTPS: A TLS context which requires server certificates signed by the certificate authority certificate associated with the active context's cluster. """ server = config.cluster["server"] base_url = URL.fromText(native_string_to_unicode(server)) ca_certs = pem.parse(config.cluster["certificate-authority"].bytes()) if not ca_certs: raise ValueError("No certificate authority certificate found.") ca_cert = ca_certs[0] try: # Validate the certificate so we have early failures for garbage data. ssl.Certificate.load(ca_cert.as_bytes(), FILETYPE_PEM) except OpenSSLError as e: raise ValueError( "Invalid certificate authority certificate found.", str(e), ) netloc = NetLocation(host=base_url.host, port=base_url.port) policy = ClientCertificatePolicyForHTTPS( credentials={}, trust_roots={ netloc: ca_cert, }, ) return policy
def requestRedirectError(self, request, failure): """ Redirect. """ url = URL.fromText(failure.value.args[0].decode("utf-8")) return self.redirect(request, url)
class URLs(object): """ Incident Management System URL schema. """ root = URL.fromText(u"/") prefix = URL.fromText(u"/ims/") styleSheet = prefix.child(u"style.css") logo = prefix.child(u"logo.png") login = prefix.child(u"login") logout = prefix.child(u"logout") jqueryBase = prefix.child(u"jquery").child(u"") jqueryJS = jqueryBase.child(u"jquery.min.js") jqueryMap = jqueryBase.child(u"jquery.min.map") bootstrapBase = prefix.child(u"bootstrap").child(u"") bootstrapCSS = bootstrapBase.child(u"css", u"bootstrap.min.css") bootstrapJS = bootstrapBase.child(u"js", u"bootstrap.min.js") dataTablesBase = prefix.child(u"datatables").child(u"") dataTablesJS = dataTablesBase.child(u"media", u"js", u"jquery.dataTables.min.js") dataTablesbootstrapCSS = dataTablesBase.child( u"media", u"css", u"dataTables.bootstrap.min.css") dataTablesbootstrapJS = dataTablesBase.child( u"media", u"js", u"dataTables.bootstrap.min.js") momentJS = prefix.child(u"moment.min.js") lscacheJS = prefix.child(u"lscache.min.js") # API endpoints api = prefix.child(u"api").child(u"") ping = api.child(u"ping").child(u"") acl = api.child(u"access") streets = api.child(u"streets") personnel = api.child(u"personnel").child(u"") incidentTypes = api.child(u"incident_types").child(u"") incidentReports = api.child(u"incident_reports").child(u"") incidentReport = incidentReports.child(u"<number>") events = api.child(u"events").child(u"") event = events.child(u"<eventID>").child(u"") locations = event.child(u"locations").child(u"") incidents = event.child(u"incidents").child(u"") incidentNumber = incidents.child(u"<number>") eventSource = api.child(u"eventsource") # Web UI imsJS = prefix.child(u"ims.js") admin = prefix.child(u"admin").child(u"") adminJS = admin.child(u"admin.js") adminAccessControl = admin.child(u"access") adminAccessControlJS = admin.child(u"access.js") adminIncidentTypes = admin.child(u"types") adminIncidentTypesJS = admin.child(u"types.js") adminStreets = admin.child(u"streets") adminStreetsJS = admin.child(u"streets.js") viewEvents = prefix.child(u"events").child(u"") viewEvent = viewEvents.child(u"<eventID>").child(u"") viewDispatchQueue = viewEvent.child(u"queue") viewDispatchQueueTemplate = prefix.child(u"queue.html") viewDispatchQueueJS = prefix.child(u"queue.js") viewDispatchQueueRelative = URL.fromText(u"queue") viewIncidents = viewEvent.child(u"incidents").child(u"") viewIncidentNumber = viewIncidents.child(u"<number>") viewIncidentNumberTemplate = prefix.child(u"incident.html") viewIncidentNumberJS = prefix.child(u"incident.js") viewIncidentReports = prefix.child(u"incident_reports").child(u"") viewIncidentReport = viewIncidentReports.child(u"<number>") viewIncidentReportTemplate = prefix.child(u"incident_report.html") viewIncidentReportJS = prefix.child(u"incident_report.js")
def request(self, method, url, **kwargs): method = method.encode('ascii').upper() # Join parameters provided in the URL # and the ones passed as argument. params = kwargs.get('params') if params: url = _combine_query_params(url, params) if isinstance(url, unicode): url = URL.fromText(url).asURI().asText().encode('ascii') # Convert headers dictionary to # twisted raw headers format. headers = kwargs.get('headers') if headers: if isinstance(headers, dict): h = Headers({}) for k, v in headers.items(): if isinstance(k, unicode): k = k.encode('ascii') if isinstance(v, bytes): h.addRawHeader(k, v) elif isinstance(v, unicode): h.addRawHeader(k, v.encode('ascii')) elif isinstance(v, list): cleanHeaders = [] for item in v: if isinstance(item, unicode): cleanHeaders.append(item.encode('ascii')) else: cleanHeaders.append(item) h.setRawHeaders(k, cleanHeaders) else: h.setRawHeaders(k, v) headers = h else: headers = Headers({}) # Here we choose a right producer # based on the parameters passed in. bodyProducer = None data = kwargs.get('data') files = kwargs.get('files') if files: # If the files keyword is present we will issue a # multipart/form-data request as it suits better for cases # with files and/or large objects. files = list(_convert_files(files)) boundary = str(uuid.uuid4()).encode('ascii') headers.setRawHeaders( b'content-type', [ b'multipart/form-data; boundary=' + boundary]) if data: data = _convert_params(data) else: data = [] bodyProducer = multipart.MultiPartProducer( data + files, boundary=boundary) elif data: # Otherwise stick to x-www-form-urlencoded format # as it's generally faster for smaller requests. if isinstance(data, (dict, list, tuple)): headers.setRawHeaders( b'content-type', [b'application/x-www-form-urlencoded']) data = urlencode(data, doseq=True) bodyProducer = self._data_to_body_producer(data) cookies = kwargs.get('cookies', {}) if not isinstance(cookies, CookieJar): cookies = cookiejar_from_dict(cookies) cookies = merge_cookies(self._cookiejar, cookies) wrapped_agent = CookieAgent(self._agent, cookies) if kwargs.get('allow_redirects', True): if kwargs.get('browser_like_redirects', False): wrapped_agent = BrowserLikeRedirectAgent(wrapped_agent) else: wrapped_agent = RedirectAgent(wrapped_agent) wrapped_agent = ContentDecoderAgent(wrapped_agent, [(b'gzip', GzipDecoder)]) auth = kwargs.get('auth') if auth: wrapped_agent = add_auth(wrapped_agent, auth) d = wrapped_agent.request( method, url, headers=headers, bodyProducer=bodyProducer) timeout = kwargs.get('timeout') if timeout: delayedCall = default_reactor(kwargs.get('reactor')).callLater( timeout, d.cancel) def gotResult(result): if delayedCall.active(): delayedCall.cancel() return result d.addBoth(gotResult) if not kwargs.get('unbuffered', False): d.addCallback(_BufferedResponse) return d.addCallback(_Response, cookies)
from twisted.web.client import Agent, HTTPConnectionPool from twisted.web.http_headers import Headers from txacme import __version__ from txacme.logging import ( LOG_ACME_ANSWER_CHALLENGE, LOG_ACME_CONSUME_DIRECTORY, LOG_ACME_CREATE_AUTHORIZATION, LOG_ACME_FETCH_CHAIN, LOG_ACME_POLL_AUTHORIZATION, LOG_ACME_REGISTER, LOG_ACME_REQUEST_CERTIFICATE, LOG_ACME_UPDATE_REGISTRATION, LOG_HTTP_PARSE_LINKS, LOG_JWS_ADD_NONCE, LOG_JWS_CHECK_RESPONSE, LOG_JWS_GET, LOG_JWS_GET_NONCE, LOG_JWS_HEAD, LOG_JWS_POST, LOG_JWS_REQUEST, LOG_JWS_SIGN) from txacme.util import tap LETSENCRYPT_DIRECTORY = URL.fromText( u'https://acme-v01.api.letsencrypt.org/directory') LETSENCRYPT_STAGING_DIRECTORY = URL.fromText( u'https://acme-staging.api.letsencrypt.org/directory') # Borrowed from requests, with modifications. def _parse_header_links(response): """ Parse the links from a Link: header field. .. todo:: Links with the same relation collide at the moment. :param bytes value: The header value.
def urlFromBytes(b): return URL.fromText(b.decode("utf-8"))
def test_wormhole_tahoe_configuration( self, customer_email, customer_id, subscription_id, old_secrets, introducer_port_number, storage_port_number, ): """ The wormhole signup mechanism sends a JSON blob of Tahoe-LAFS configuration via a magic wormhole identified by a wormhole code produced during signup. """ assume(introducer_port_number != storage_port_number) provisioned = [] def provision_subscription( smclient, subscription, ): p = attr.assoc( subscription, introducer_port_number=introducer_port_number, storage_port_number=storage_port_number, oldsecrets=old_secrets, ) provisioned.append(p) return succeed(p) plan_identifier = u"foobar" reactor = Clock() server = MemoryWormholeServer() provisioner = get_provisioner( reactor, URL.fromText(u"http://subscription-manager/"), provision_subscription, ) signup = get_wormhole_signup( reactor, provisioner, server, URL.fromText(u"ws://foo.invalid/"), FilePath(self.mktemp()), ) d = signup.signup(customer_email, customer_id, subscription_id, plan_identifier) wormhole_claim = self.successResultOf(d) wh = server.create( APPID, u"ws://foo.invalid/", reactor, ) wh.set_code(wormhole_claim.code) d = wh.when_code() def foo(x): wh.send_message('{"abilities": {"client-v1": {}}}') return wh.get_message() d.addCallback(foo) def bar(arg): self.assertEqual( loads(arg), {"abilities": {"server-v1":{}}} ) return wh.get_message() d.addCallback(bar) received = self.successResultOf(d) received_config = loads(received) self.assertThat( received_config["introducer"], Equals(provisioned[0].external_introducer_furl), )
class SiteOptions(Options): optFlags = [ # TODO: # Make this HTTP-only. # Terminate TLS externally. # On K8S on AWS, consider using # http://kubernetes.io/docs/user-guide/services/#ssl-support-on-aws ] optParameters = [ ("stripe-secret-api-key-path", None, None, "A path to a file containing a Stripe API key.", FilePath), ("stripe-publishable-api-key-path", None, None, "A path to a file containing a publishable Stripe API key.", FilePath), ("site-logs-path", None, None, "A path to a file to which HTTP logs for the site will be written.", FilePath), ("wormhole-result-path", None, None, "A path to a file to which wormhole interaction results will be written.", FilePath, ), ("redirect-to-port", None, None, "A TCP port number to which to redirect for the TLS site.", int), ("subscription-manager", None, None, "Base URL of the subscription manager API.", urlFromBytes, ), ("rendezvous-url", None, URL.fromText(u"ws://wormhole.leastauthority.com:4000/v1"), "The URL of the Wormhole Rendezvous server for wormhole-based signup.", urlFromBytes, ), ("metrics-port", None, "tcp:9000", "A server endpoint description string on which to run a metrics-exposing server.", ), ] def __init__(self, reactor): Options.__init__(self) self.reactor = reactor self["secure-ports"] = [] self["insecure-ports"] = [] opt_eliot_destination = opt_eliot_destination def _parse_endpoint(self, label, description): """ Parse a Twisted endpoint description string into an endpoint or convert the parse error into a raised L{UsageError}. """ try: return serverFromString(self.reactor, description) except Exception as e: raise UsageError( u"Could not parse {label} value {description}: {error}".format( label=label, description=description, error=str(e), ) ) def opt_secure_port(self, endpoint_description): """ A Twisted endpoint description string describing an address at which to listen for secure web client connections. The website will be served here. This option must be used at least once. """ endpoint = self._parse_endpoint(u"secure-port", endpoint_description) self["secure-ports"].append(endpoint) def opt_insecure_port(self, endpoint_description): """ A Twisted endpoint description string describing an address at which to listen for insecure web client connections. A redirect will be returned sending the client to a secure location where the website can be accessed. This option may be used zero or more times. """ endpoint = self._parse_endpoint(u"insecure-port", endpoint_description) self["insecure-ports"].append(endpoint) def postOptions(self): required_options = [ "stripe-secret-api-key-path", "stripe-publishable-api-key-path", "subscription-manager", "site-logs-path", "wormhole-result-path", ] for option in required_options: if self[option] is None: raise UsageError("Missing required option --{}".format(option)) if not self["secure-ports"]: raise UsageError( u"Use --secure-port at least once to specify an address for " u"the website." ) if self["redirect-to-port"] is not None and not self["insecure-ports"]: raise UsageError( u"Use --insecure-port at least once or there is no server to " u"use --redirect-to-port value." ) p = self["site-logs-path"].parent() if not p.isdir(): p.makedirs()
def test_wormhole_tahoe_configuration( self, customer_email, customer_id, subscription_id, old_secrets, introducer_port_number, storage_port_number, ): """ The wormhole signup mechanism sends a JSON blob of Tahoe-LAFS configuration via a magic wormhole identified by a wormhole code produced during signup. """ assume(introducer_port_number != storage_port_number) provisioned = [] def provision_subscription( smclient, subscription, ): p = attr.assoc( subscription, introducer_port_number=introducer_port_number, storage_port_number=storage_port_number, oldsecrets=old_secrets, ) provisioned.append(p) return succeed(p) plan_identifier = u"foobar" reactor = Clock() server = MemoryWormholeServer() provisioner = get_provisioner( reactor, URL.fromText(u"http://subscription-manager/"), provision_subscription, ) signup = get_wormhole_signup( reactor, provisioner, server, URL.fromText(u"ws://foo.invalid/"), FilePath(self.mktemp()), ) d = signup.signup(customer_email, customer_id, subscription_id, plan_identifier) wormhole_claim = self.successResultOf(d) wh = server.create( u"tahoe-lafs.org/tahoe-lafs/v1", u"ws://foo.invalid/", reactor, ) wh.set_code(wormhole_claim.code) d = wh.when_code() def foo(x): wh.send_message('{"abilities": {"client-v1": {}}}') return wh.get_message() d.addCallback(foo) def bar(arg): self.assertEqual( loads(arg), {"abilities": {"server-v1":{}}} ) return wh.get_message() d.addCallback(bar) received = self.successResultOf(d) received_config = loads(received) self.assertThat( received_config["introducer"], Equals(provisioned[0].external_introducer_furl), )
def _url(self, *segments): return URL.fromText(self.endpoint.decode("utf-8")).child( *segments).asURI().asText().encode("ascii")
def get_things_done(): """ Here is where the client part is setup and action is done. """ responders = yield start_responders() # We first validate the directory. account_key = _get_account_key() try: client = yield Client.from_url( reactor, URL.fromText(acme_url.decode('utf-8')), key=JWKRSA(key=account_key), alg=RS256, ) except Exception as error: print('\n\nFailed to connect to ACME directory. %s' % (error, )) yield reactor.stop() defer.returnValue(None) # Then we register a new account or update an existing account. # First register a new account with a contact set, then using the same # key call register with a different contact and see that it was updated. response = yield client.start( email='[email protected],[email protected]') print('Account URI: %s' % (response.uri, )) print('Account contact: %s' % (response.body.contact, )) # We request a single certificate for a list of domains and get an "order" cert_key = generate_private_key('rsa') orderr = yield client.submit_order(cert_key, requested_domains) # Each order had a list of "authorizations" for which the challenge needs # to be validated. for authorization in orderr.authorizations: try: # Make sure all ACME server requests are sequential. # For now, answering to the challenges in parallel will not work. yield answer_challenge(authorization, client, responders, clock=reactor) except Exception as error: print('\n\nFailed to validate a challenge. %s' % (error, )) yield reactor.stop() defer.returnValue(None) certificate = yield get_certificate(orderr, client, clock=reactor) print('Got a new cert:\n') print(certificate.body) cert_key_pem = cert_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) # Cleanup the client and disconnect any persistent connection to the # ACME server. yield client.stop() # The new certificate is available and we can start a demo HTTPS server # using it. yield start_https_demo_server(cert_key_pem, certificate.body) print('txacme demo done.')
from twisted.python.url import URL LETSENCRYPT_DIRECTORY = URL.fromText( u'https://acme-v02.api.letsencrypt.org/directory') LETSENCRYPT_STAGING_DIRECTORY = URL.fromText( u'https://acme-staging-v02.api.letsencrypt.org/directory') __all__ = ['LETSENCRYPT_DIRECTORY', 'LETSENCRYPT_STAGING_DIRECTORY']
def fetch_title(self, url, hostname_tag=False, friendly_errors=False): """Fetch the document at *url* and return a `Deferred` yielding the document title or summary as a Unicode string. *url* may be a Unicode string IRI, a byte string URI, or a Twisted `URL`. If *hostname_tag* is true, prefix the extracted title with the hostname of the initially requested URI or IRI in the form that was originally provided, as well as the hostname of the final ASCII-only URI if it differs due to redirects or normalization. If *friendly_errors* is true, catch common connection errors and return a description of the error as the extracted title instead of reraising. Otherwise, all errors bubble to the caller. """ title = None if isinstance(url, unicode): url = URL.fromText(url) elif isinstance(url, str): url = URL.fromText(url.decode('ascii')) current = url response = None for _ in xrange(self.max_soft_redirects): last_response = response # This encoding should be safe, since asURI() only returns # URIs with ASCII code points. request = self.agent.request( 'GET', current.asURI().asText().encode('ascii')) if friendly_errors: request.addErrback(describe_error) response = yield request if isinstance(response, basestring): # We got an error message from describe_error. Bail. title = response break response.setPreviousResponse(last_response) content_type = cgi.parse_header( response.headers.getRawHeaders('Content-Type', [''])[0])[0] if content_type in self.extractors: extractor = self.extractors[content_type] extracted = yield extractor.extract(response) if isinstance(extracted, Redirect): current = URL.fromText( response.request.absoluteURI.decode('ascii')).click( extracted.location) continue title = extracted # The only case where we'd want to loop again is when the # response returned is a soft redirect. break else: if friendly_errors: title = u'Encountered too many redirects.' else: raise ResponseFailed([Failure(InfiniteRedirection( 599, 'Too many soft redirects', location=current.asURI().asText().encode('ascii')))]) if title is None: title = u'{} document'.format(content_type or u'Unknown') if response.length is not UNKNOWN_LENGTH: title += u' ({})'.format(filesize(response.length)) if hostname_tag: tag = url.host if isinstance(response, Response): initial = url.host final = URL.fromText( response.request.absoluteURI.decode('ascii')).host if initial != final: tag = u'{} \u2192 {}'.format(initial, final) title = u'[{}] {}'.format(tag, title) returnValue(title)
def request(self, method, url, **kwargs): method = method.encode('ascii').upper() # Join parameters provided in the URL # and the ones passed as argument. params = kwargs.get('params') if params: url = _combine_query_params(url, params) if isinstance(url, unicode): url = URL.fromText(url).asURI().asText().encode('ascii') # Convert headers dictionary to # twisted raw headers format. headers = kwargs.get('headers') if headers: if isinstance(headers, dict): h = Headers({}) for k, v in headers.items(): if isinstance(v, (bytes, unicode)): h.addRawHeader(k, v) elif isinstance(v, list): h.setRawHeaders(k, v) headers = h else: headers = Headers({}) # Here we choose a right producer # based on the parameters passed in. bodyProducer = None data = kwargs.get('data') files = kwargs.get('files') # since json=None needs to be serialized as 'null', we need to # explicitly check kwargs for this key has_json = 'json' in kwargs if files: # If the files keyword is present we will issue a # multipart/form-data request as it suits better for cases # with files and/or large objects. files = list(_convert_files(files)) boundary = str(uuid.uuid4()).encode('ascii') headers.setRawHeaders( b'content-type', [ b'multipart/form-data; boundary=' + boundary]) if data: data = _convert_params(data) else: data = [] bodyProducer = multipart.MultiPartProducer( data + files, boundary=boundary) elif data: # Otherwise stick to x-www-form-urlencoded format # as it's generally faster for smaller requests. if isinstance(data, (dict, list, tuple)): headers.setRawHeaders( b'content-type', [b'application/x-www-form-urlencoded']) data = urlencode(data, doseq=True) bodyProducer = self._data_to_body_producer(data) elif has_json: # If data is sent as json, set Content-Type as 'application/json' headers.setRawHeaders( b'content-type', [b'application/json; charset=UTF-8']) content = kwargs['json'] json = json_dumps(content, separators=(u',', u':')).encode('utf-8') bodyProducer = self._data_to_body_producer(json) cookies = kwargs.get('cookies', {}) if not isinstance(cookies, CookieJar): cookies = cookiejar_from_dict(cookies) cookies = merge_cookies(self._cookiejar, cookies) wrapped_agent = CookieAgent(self._agent, cookies) if kwargs.get('allow_redirects', True): if kwargs.get('browser_like_redirects', False): wrapped_agent = BrowserLikeRedirectAgent(wrapped_agent) else: wrapped_agent = RedirectAgent(wrapped_agent) wrapped_agent = ContentDecoderAgent(wrapped_agent, [(b'gzip', GzipDecoder)]) auth = kwargs.get('auth') if auth: wrapped_agent = add_auth(wrapped_agent, auth) d = wrapped_agent.request( method, url, headers=headers, bodyProducer=bodyProducer) timeout = kwargs.get('timeout') if timeout: delayedCall = default_reactor(kwargs.get('reactor')).callLater( timeout, d.cancel) def gotResult(result): if delayedCall.active(): delayedCall.cancel() return result d.addBoth(gotResult) if not kwargs.get('unbuffered', False): d.addCallback(_BufferedResponse) return d.addCallback(_Response, cookies)
def test_emailed_introducer_furl( self, customer_email, customer_id, subscription_id, old_secrets, introducer_port_number, storage_port_number, ): """ The email signup mechanism sends an activation email including an introducer furl which points at the server and port identified by the activated subscription detail object. """ assume(introducer_port_number != storage_port_number) emails = [] def provision_subscription( smclient, subscription, ): return succeed( attr.assoc( subscription, introducer_port_number=introducer_port_number, storage_port_number=storage_port_number, oldsecrets=old_secrets, ), ) def send_signup_confirmation( customer_email, external_introducer_furl, customer_keyinfo, stdout, stderr, ): emails.append((customer_email, "success", external_introducer_furl)) return succeed(None) def send_notify_failure( reason, customer_email, logfilename, stdout, stderr, ): emails.append((customer_email, "failure", reason)) return succeed(None) plan_identifier = u"foobar" reactor = object() signup = get_email_signup( reactor, get_provisioner( reactor, URL.fromText(u"http://subscription-manager/"), provision_subscription, ), send_signup_confirmation, send_notify_failure, ) d = signup.signup(customer_email, customer_id, subscription_id, plan_identifier) self.successResultOf(d) [(recipient, result, rest)] = emails self.expectThat(recipient, Equals(customer_email)) self.expectThat(result, Equals("success")) def get_hint_port(furl): tub_id, location_hints, name = decode_furl(furl) host, port = location_hints[0].split(u":") return int(port) self.expectThat( rest, AfterPreprocessing( get_hint_port, Equals(introducer_port_number), ), )
class ExternalMixIn(object): """ Mix-in for cached external resources. """ bootstrapVersionNumber = u"3.3.7" jqueryVersionNumber = u"3.1.0" dataTablesVersionNumber = u"1.10.12" momentVersionNumber = u"2.14.1" lscacheVersionNumber = u"1.0.5" bootstrapVersion = u"bootstrap-{}-dist".format(bootstrapVersionNumber) jqueryVersion = u"jquery-{}".format(jqueryVersionNumber) dataTablesVersion = u"DataTables-{}".format(dataTablesVersionNumber) momentVersion = u"moment-{}".format(momentVersionNumber) lscacheVersion = u"lscache-{}".format(lscacheVersionNumber) bootstrapSourceURL = URL.fromText( u"https://github.com/twbs/bootstrap/releases/download/v{n}/{v}.zip". format(n=bootstrapVersionNumber, v=bootstrapVersion)) jqueryJSSourceURL = URL.fromText( u"https://code.jquery.com/{v}.min.js".format(n=jqueryVersionNumber, v=jqueryVersion)) jqueryMapSourceURL = URL.fromText( u"https://code.jquery.com/{v}.min.map".format(n=jqueryVersionNumber, v=jqueryVersion)) dataTablesSourceURL = URL.fromText( u"https://datatables.net/releases/DataTables-{n}.zip".format( n=dataTablesVersionNumber, v=dataTablesVersion)) momentJSSourceURL = URL.fromText( u"https://cdnjs.cloudflare.com/ajax/libs/moment.js/{n}/moment.min.js". format(n=momentVersionNumber)) lscacheJSSourceURL = URL.fromText( u"https://raw.githubusercontent.com/pamelafox/lscache/{n}/" u"lscache.min.js".format(n=lscacheVersionNumber)) @route(URLs.bootstrapBase.asText(), methods=("HEAD", "GET"), branch=True) @staticResource def bootstrapResource(self, request): requestURL = URL.fromText(request.uri) # Remove URL prefix names = requestURL.path[len(URLs.bootstrapBase.path) - 1:] request.setHeader(HeaderName.contentType.value, ContentType.CSS.value) return self.cachedZippedResource(request, self.bootstrapSourceURL, self.bootstrapVersion, self.bootstrapVersion, *names) @route(URLs.jqueryJS.asText(), methods=("HEAD", "GET")) @staticResource def jqueryJSResource(self, request): request.setHeader(HeaderName.contentType.value, ContentType.JavaScript.value) return self.cachedResource( request, self.jqueryJSSourceURL, "{}.min.js".format(self.jqueryVersion), ) @route(URLs.jqueryMap.asText(), methods=("HEAD", "GET")) @staticResource def jqueryMapResource(self, request): request.setHeader(HeaderName.contentType.value, ContentType.JSON.value) return self.cachedResource( request, self.jqueryMapSourceURL, "{}.min.map".format(self.jqueryVersion), ) @route(URLs.dataTablesBase.asText(), methods=("HEAD", "GET"), branch=True) @staticResource def dataTablesResource(self, request): requestURL = URL.fromText(request.uri) # Remove URL prefix names = requestURL.path[len(URLs.dataTablesBase.path) - 1:] request.setHeader(HeaderName.contentType.value, ContentType.CSS.value) return self.cachedZippedResource(request, self.dataTablesSourceURL, self.dataTablesVersion, self.dataTablesVersion, *names) @route(URLs.momentJS.asText(), methods=("HEAD", "GET")) @staticResource def momentJSResource(self, request): request.setHeader(HeaderName.contentType.value, ContentType.JavaScript.value) return self.cachedResource( request, self.momentJSSourceURL, "{}.min.js".format(self.momentVersion), ) @route(URLs.lscacheJS.asText(), methods=("HEAD", "GET")) @staticResource def lscacheJSResource(self, request): request.setHeader(HeaderName.contentType.value, ContentType.JavaScript.value) return self.cachedResource( request, self.lscacheJSSourceURL, "{}.min.js".format(self.lscacheVersion), ) @inlineCallbacks def cacheFromURL(self, url, name): cacheDir = self.config.CachedResources if not cacheDir.isdir(): cacheDir.createDirectory() destination = cacheDir.child(name) if not destination.exists(): tmp = destination.temporarySibling(extension=".tmp") try: yield downloadPage(url.asText().encode("utf-8"), tmp.open("w")) except: self.log.failure("Download failed for {url}", url=url) try: tmp.remove() except (OSError, IOError): pass else: tmp.moveTo(destination) returnValue(destination) @inlineCallbacks def cachedResource(self, request, url, name): filePath = yield self.cacheFromURL(url, name) try: returnValue(filePath.getContent()) except (OSError, IOError) as e: self.log.error( "Unable to open file {filePath.path}: {error}", filePath=filePath, error=e, ) returnValue(self.notFoundResource(request)) @inlineCallbacks def cachedZippedResource(self, request, url, archiveName, name, *names): archivePath = yield self.cacheFromURL(url, "{0}.zip".format(archiveName)) try: filePath = ZipArchive(archivePath.path) except BadZipfile as e: self.log.error( "Corrupt zip archive {archive.path}: {error}", archive=archivePath, error=e, ) try: archivePath.remove() except (OSError, IOError): pass returnValue(self.notFoundResource(request)) except (OSError, IOError) as e: self.log.error( "Unable to open zip archive {archive.path}: {error}", archive=archivePath, error=e, ) returnValue(self.notFoundResource(request)) filePath = filePath.child(name) for name in names: filePath = filePath.child(name) try: returnValue(filePath.getContent()) except KeyError: self.log.error( "File not found in ZIP archive: {filePath.path}", filePath=filePath, archive=archivePath, ) returnValue(self.notFoundResource(request))