Martin Blanchard pushed to branch mablanch/144-jwt-authentication at BuildGrid / buildgrid
Commits:
- 
302a1965
by Martin Blanchard at 2018-11-29T17:18:20Z
- 
3fe9b1cb
by Martin Blanchard at 2018-11-29T17:18:25Z
- 
17958869
by Martin Blanchard at 2018-11-29T17:18:25Z
- 
550e581c
by Martin Blanchard at 2018-11-29T17:18:25Z
- 
820c3a29
by Martin Blanchard at 2018-11-29T17:18:25Z
- 
28f9c52e
by Martin Blanchard at 2018-11-29T17:18:25Z
6 changed files:
- buildgrid/_app/commands/cmd_capabilities.py
- buildgrid/_app/commands/cmd_cas.py
- buildgrid/_app/commands/cmd_execute.py
- buildgrid/_app/commands/cmd_operation.py
- + buildgrid/client/authentication.py
- buildgrid/server/instance.py
Changes:
| ... | ... | @@ -17,9 +17,12 @@ import sys | 
| 17 | 17 |  from urllib.parse import urlparse
 | 
| 18 | 18 |  | 
| 19 | 19 |  import click
 | 
| 20 | +from google.protobuf import json_format
 | |
| 20 | 21 |  import grpc
 | 
| 21 | 22 |  | 
| 23 | +from buildgrid.client.authentication import setup_channel
 | |
| 22 | 24 |  from buildgrid.client.capabilities import CapabilitiesInterface
 | 
| 25 | +from buildgrid._exceptions import InvalidArgumentError
 | |
| 23 | 26 |  | 
| 24 | 27 |  from ..cli import pass_context
 | 
| 25 | 28 |  | 
| ... | ... | @@ -27,32 +30,29 @@ from ..cli import pass_context | 
| 27 | 30 |  @click.command(name='capabilities', short_help="Capabilities service.")
 | 
| 28 | 31 |  @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
 | 
| 29 | 32 |                help="Remote execution server's URL (port defaults to 50051 if no specified).")
 | 
| 33 | +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
 | |
| 34 | +              help="Authorization token for the remote.")
 | |
| 30 | 35 |  @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 31 | -              help="Private client key for TLS (PEM-encoded)")
 | |
| 36 | +              help="Private client key for TLS (PEM-encoded).")
 | |
| 32 | 37 |  @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 33 | -              help="Public client certificate for TLS (PEM-encoded)")
 | |
| 38 | +              help="Public client certificate for TLS (PEM-encoded).")
 | |
| 34 | 39 |  @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 35 | -              help="Public server certificate for TLS (PEM-encoded)")
 | |
| 40 | +              help="Public server certificate for TLS (PEM-encoded).")
 | |
| 36 | 41 |  @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
 | 
| 37 | 42 |                help="Targeted farm instance name.")
 | 
| 38 | 43 |  @pass_context
 | 
| 39 | -def cli(context, remote, instance_name, client_key, client_cert, server_cert):
 | |
| 40 | -    click.echo("Getting capabilities...")
 | |
| 41 | -    url = urlparse(remote)
 | |
| 42 | - | |
| 43 | -    remote = '{}:{}'.format(url.hostname, url.port or 50051)
 | |
| 44 | -    instance_name = instance_name
 | |
| 45 | - | |
| 46 | -    if url.scheme == 'http':
 | |
| 47 | -        channel = grpc.insecure_channel(remote)
 | |
| 48 | -    else:
 | |
| 49 | -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
 | |
| 50 | -        if not credentials:
 | |
| 51 | -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
 | |
| 52 | -            sys.exit(-1)
 | |
| 53 | - | |
| 54 | -        channel = grpc.secure_channel(remote, credentials)
 | |
| 55 | - | |
| 56 | -    interface = CapabilitiesInterface(channel)
 | |
| 57 | -    response = interface.get_capabilities(instance_name)
 | |
| 58 | -    click.echo(response) | |
| 44 | +def cli(context, remote, instance_name, auth_token, client_key, client_cert, server_cert):
 | |
| 45 | +    """Entry point for the bgd-capabilities CLI command group."""
 | |
| 46 | +    try:
 | |
| 47 | +        context.channel = setup_channel(remote, authorization_token=auth_token,
 | |
| 48 | +                                        client_key=client_key, client_cert=client_cert)
 | |
| 49 | + | |
| 50 | +    except InvalidArgumentError as e:
 | |
| 51 | +        click.echo("Error: {}.".format(e), err=True)
 | |
| 52 | + | |
| 53 | +    context.instance_name = instance_name
 | |
| 54 | + | |
| 55 | +    interface = CapabilitiesInterface(context.channel)
 | |
| 56 | +    response = interface.get_capabilities(context.instance_name)
 | |
| 57 | + | |
| 58 | +    click.echo(json_format.MessageToJson(response)) | 
| ... | ... | @@ -27,7 +27,9 @@ from urllib.parse import urlparse | 
| 27 | 27 |  import click
 | 
| 28 | 28 |  import grpc
 | 
| 29 | 29 |  | 
| 30 | +from buildgrid.client.authentication import setup_channel
 | |
| 30 | 31 |  from buildgrid.client.cas import download, upload
 | 
| 32 | +from buildgrid._exceptions import InvalidArgumentError
 | |
| 31 | 33 |  from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
 | 
| 32 | 34 |  from buildgrid.utils import create_digest, merkle_tree_maker, read_file
 | 
| 33 | 35 |  | 
| ... | ... | @@ -37,32 +39,27 @@ from ..cli import pass_context | 
| 37 | 39 |  @click.group(name='cas', short_help="Interact with the CAS server.")
 | 
| 38 | 40 |  @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
 | 
| 39 | 41 |                help="Remote execution server's URL (port defaults to 50051 if no specified).")
 | 
| 42 | +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
 | |
| 43 | +              help="Authorization token for the remote.")
 | |
| 40 | 44 |  @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 41 | -              help="Private client key for TLS (PEM-encoded)")
 | |
| 45 | +              help="Private client key for TLS (PEM-encoded).")
 | |
| 42 | 46 |  @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 43 | -              help="Public client certificate for TLS (PEM-encoded)")
 | |
| 47 | +              help="Public client certificate for TLS (PEM-encoded).")
 | |
| 44 | 48 |  @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 45 | 49 |                help="Public server certificate for TLS (PEM-encoded)")
 | 
| 46 | 50 |  @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
 | 
| 47 | 51 |                help="Targeted farm instance name.")
 | 
| 48 | 52 |  @pass_context
 | 
| 49 | -def cli(context, remote, instance_name, client_key, client_cert, server_cert):
 | |
| 50 | -    url = urlparse(remote)
 | |
| 53 | +def cli(context, remote, instance_name, auth_token, client_key, client_cert, server_cert):
 | |
| 54 | +    """Entry point for the bgd-cas CLI command group."""
 | |
| 55 | +    try:
 | |
| 56 | +        context.channel = setup_channel(remote, authorization_token=auth_token,
 | |
| 57 | +                                        client_key=client_key, client_cert=client_cert)
 | |
| 51 | 58 |  | 
| 52 | -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
 | |
| 53 | -    context.instance_name = instance_name
 | |
| 54 | - | |
| 55 | -    if url.scheme == 'http':
 | |
| 56 | -        context.channel = grpc.insecure_channel(context.remote)
 | |
| 57 | -    else:
 | |
| 58 | -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
 | |
| 59 | -        if not credentials:
 | |
| 60 | -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
 | |
| 61 | -            sys.exit(-1)
 | |
| 59 | +    except InvalidArgumentError as e:
 | |
| 60 | +        click.echo("Error: {}.".format(e), err=True)
 | |
| 62 | 61 |  | 
| 63 | -        context.channel = grpc.secure_channel(context.remote, credentials)
 | |
| 64 | - | |
| 65 | -    click.echo("Starting for remote=[{}]".format(context.remote))
 | |
| 62 | +    context.instance_name = instance_name
 | |
| 66 | 63 |  | 
| 67 | 64 |  | 
| 68 | 65 |  @cli.command('upload-dummy', short_help="Upload a dummy action. Should be used with `execute dummy-request`")
 | 
| ... | ... | @@ -28,7 +28,9 @@ from urllib.parse import urlparse | 
| 28 | 28 |  import click
 | 
| 29 | 29 |  import grpc
 | 
| 30 | 30 |  | 
| 31 | +from buildgrid.client.authentication import setup_channel
 | |
| 31 | 32 |  from buildgrid.client.cas import download, upload
 | 
| 33 | +from buildgrid._exceptions import InvalidArgumentError
 | |
| 32 | 34 |  from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
 | 
| 33 | 35 |  from buildgrid.utils import create_digest
 | 
| 34 | 36 |  | 
| ... | ... | @@ -38,32 +40,27 @@ from ..cli import pass_context | 
| 38 | 40 |  @click.group(name='execute', short_help="Execute simple operations.")
 | 
| 39 | 41 |  @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
 | 
| 40 | 42 |                help="Remote execution server's URL (port defaults to 50051 if no specified).")
 | 
| 43 | +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
 | |
| 44 | +              help="Authorization token for the remote.")
 | |
| 41 | 45 |  @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 42 | -              help="Private client key for TLS (PEM-encoded)")
 | |
| 46 | +              help="Private client key for TLS (PEM-encoded).")
 | |
| 43 | 47 |  @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 44 | -              help="Public client certificate for TLS (PEM-encoded)")
 | |
| 48 | +              help="Public client certificate for TLS (PEM-encoded).")
 | |
| 45 | 49 |  @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 46 | -              help="Public server certificate for TLS (PEM-encoded)")
 | |
| 50 | +              help="Public server certificate for TLS (PEM-encoded).")
 | |
| 47 | 51 |  @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
 | 
| 48 | 52 |                help="Targeted farm instance name.")
 | 
| 49 | 53 |  @pass_context
 | 
| 50 | 54 |  def cli(context, remote, instance_name, client_key, client_cert, server_cert):
 | 
| 51 | -    url = urlparse(remote)
 | |
| 55 | +    """Entry point for the bgd-execute CLI command group."""
 | |
| 56 | +    try:
 | |
| 57 | +        context.channel = setup_channel(remote, authorization_token=auth_token,
 | |
| 58 | +                                        client_key=client_key, client_cert=client_cert)
 | |
| 52 | 59 |  | 
| 53 | -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
 | |
| 54 | -    context.instance_name = instance_name
 | |
| 55 | - | |
| 56 | -    if url.scheme == 'http':
 | |
| 57 | -        context.channel = grpc.insecure_channel(context.remote)
 | |
| 58 | -    else:
 | |
| 59 | -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
 | |
| 60 | -        if not credentials:
 | |
| 61 | -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
 | |
| 62 | -            sys.exit(-1)
 | |
| 60 | +    except InvalidArgumentError as e:
 | |
| 61 | +        click.echo("Error: {}.".format(e), err=True)
 | |
| 63 | 62 |  | 
| 64 | -        context.channel = grpc.secure_channel(context.remote, credentials)
 | |
| 65 | - | |
| 66 | -    click.echo("Starting for remote=[{}]".format(context.remote))
 | |
| 63 | +    context.instance_name = instance_name
 | |
| 67 | 64 |  | 
| 68 | 65 |  | 
| 69 | 66 |  @cli.command('request-dummy', short_help="Send a dummy action.")
 | 
| ... | ... | @@ -30,7 +30,9 @@ import click | 
| 30 | 30 |  from google.protobuf import json_format
 | 
| 31 | 31 |  import grpc
 | 
| 32 | 32 |  | 
| 33 | +from buildgrid.client.authentication import setup_channel
 | |
| 33 | 34 |  from buildgrid._enums import OperationStage
 | 
| 35 | +from buildgrid._exceptions import InvalidArgumentError
 | |
| 34 | 36 |  from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
 | 
| 35 | 37 |  from buildgrid._protos.google.longrunning import operations_pb2, operations_pb2_grpc
 | 
| 36 | 38 |  from buildgrid._protos.google.rpc import code_pb2
 | 
| ... | ... | @@ -41,32 +43,27 @@ from ..cli import pass_context | 
| 41 | 43 |  @click.group(name='operation', short_help="Long running operations commands.")
 | 
| 42 | 44 |  @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
 | 
| 43 | 45 |                help="Remote execution server's URL (port defaults to 50051 if no specified).")
 | 
| 46 | +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
 | |
| 47 | +              help="Authorization token for the remote.")
 | |
| 44 | 48 |  @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 45 | -              help="Private client key for TLS (PEM-encoded)")
 | |
| 49 | +              help="Private client key for TLS (PEM-encoded).")
 | |
| 46 | 50 |  @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 47 | -              help="Public client certificate for TLS (PEM-encoded)")
 | |
| 51 | +              help="Public client certificate for TLS (PEM-encoded).")
 | |
| 48 | 52 |  @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
 | 
| 49 | -              help="Public server certificate for TLS (PEM-encoded)")
 | |
| 53 | +              help="Public server certificate for TLS (PEM-encoded).")
 | |
| 50 | 54 |  @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
 | 
| 51 | 55 |                help="Targeted farm instance name.")
 | 
| 52 | 56 |  @pass_context
 | 
| 53 | 57 |  def cli(context, remote, instance_name, client_key, client_cert, server_cert):
 | 
| 54 | -    url = urlparse(remote)
 | |
| 58 | +    """Entry point for the bgd-operation CLI command group."""
 | |
| 59 | +    try:
 | |
| 60 | +        context.channel = setup_channel(remote, authorization_token=auth_token,
 | |
| 61 | +                                        client_key=client_key, client_cert=client_cert)
 | |
| 55 | 62 |  | 
| 56 | -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
 | |
| 57 | -    context.instance_name = instance_name
 | |
| 58 | - | |
| 59 | -    if url.scheme == 'http':
 | |
| 60 | -        context.channel = grpc.insecure_channel(context.remote)
 | |
| 61 | -    else:
 | |
| 62 | -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
 | |
| 63 | -        if not credentials:
 | |
| 64 | -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
 | |
| 65 | -            sys.exit(-1)
 | |
| 63 | +    except InvalidArgumentError as e:
 | |
| 64 | +        click.echo("Error: {}.".format(e), err=True)
 | |
| 66 | 65 |  | 
| 67 | -        context.channel = grpc.secure_channel(context.remote, credentials)
 | |
| 68 | - | |
| 69 | -    click.echo("Starting for remote=[{}]".format(context.remote))
 | |
| 66 | +    context.instance_name = instance_name
 | |
| 70 | 67 |  | 
| 71 | 68 |  | 
| 72 | 69 |  def _print_operation_status(operation, print_details=False):
 | 
| 1 | +# Copyright (C) 2018 Bloomberg LP
 | |
| 2 | +#
 | |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License");
 | |
| 4 | +# you may not use this file except in compliance with the License.
 | |
| 5 | +# You may obtain 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,
 | |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| 12 | +# See the License for the specific language governing permissions and
 | |
| 13 | +# limitations under the License.
 | |
| 14 | + | |
| 15 | + | |
| 16 | +import base64
 | |
| 17 | +from collections import namedtuple
 | |
| 18 | +from urllib.parse import urlparse
 | |
| 19 | +import os
 | |
| 20 | + | |
| 21 | +import grpc
 | |
| 22 | + | |
| 23 | +from buildgrid._exceptions import InvalidArgumentError
 | |
| 24 | +from buildgrid.utils import read_file
 | |
| 25 | + | |
| 26 | + | |
| 27 | +def load_tls_channel_credentials(client_key=None, client_cert=None, server_cert=None):
 | |
| 28 | +    """Looks-up and loads TLS gRPC client channel credentials.
 | |
| 29 | + | |
| 30 | +    Args:
 | |
| 31 | +        client_key(str, optional): Client certificate chain file path.
 | |
| 32 | +        client_cert(str, optional): Client private key file path.
 | |
| 33 | +        server_cert(str, optional): Serve root certificate file path.
 | |
| 34 | + | |
| 35 | +    Returns:
 | |
| 36 | +        ChannelCredentials: Credentials to be used for a TLS-encrypted gRPC
 | |
| 37 | +            client channel.
 | |
| 38 | +    """
 | |
| 39 | +    if server_cert and os.path.exists(server_cert):
 | |
| 40 | +        server_cert_pem = read_file(server_cert)
 | |
| 41 | +    else:
 | |
| 42 | +        server_cert_pem = None
 | |
| 43 | + | |
| 44 | +    if client_key and os.path.exists(client_key):
 | |
| 45 | +        client_key_pem = read_file(client_key)
 | |
| 46 | +    else:
 | |
| 47 | +        client_key_pem = None
 | |
| 48 | + | |
| 49 | +    if client_key_pem and client_cert and os.path.exists(client_cert):
 | |
| 50 | +        client_cert_pem = read_file(client_cert)
 | |
| 51 | +    else:
 | |
| 52 | +        client_cert_pem = None
 | |
| 53 | + | |
| 54 | +    credentials = grpc.ssl_channel_credentials(root_certificates=server_cert_pem,
 | |
| 55 | +                                               private_key=client_key_pem,
 | |
| 56 | +                                               certificate_chain=client_cert_pem)
 | |
| 57 | +    return credentials
 | |
| 58 | + | |
| 59 | + | |
| 60 | +def load_channel_authorization_token(auth_token=None):
 | |
| 61 | +    """Looks-up and loads client authorization token.
 | |
| 62 | + | |
| 63 | +    Args:
 | |
| 64 | +        auth_token (str, optional): Token file path.
 | |
| 65 | + | |
| 66 | +    Returns:
 | |
| 67 | +        str: Encoded token string.
 | |
| 68 | +    """
 | |
| 69 | +    if auth_token and os.path.exists(auth_token):
 | |
| 70 | +        return read_file(auth_token).decode()
 | |
| 71 | + | |
| 72 | +    #TODO: Try loading the token from a default location?
 | |
| 73 | + | |
| 74 | +    return None
 | |
| 75 | + | |
| 76 | + | |
| 77 | +def setup_channel(remote_url, authorization_token=None,
 | |
| 78 | +                  client_key=None, client_cert=None, server_cert=None):
 | |
| 79 | +    """Creates a new gRPC client communication chanel.
 | |
| 80 | + | |
| 81 | +    If `remote_url` does not specifies a port number, defaults 50051.
 | |
| 82 | + | |
| 83 | +    Args:
 | |
| 84 | +        remote_url (str): URL for the remote, including port and protocol.
 | |
| 85 | +        authorization_token (str): Authorization token file path.
 | |
| 86 | +        server_cert(str): TLS certificate chain file path.
 | |
| 87 | +        client_key(str): TLS root certificate file path.
 | |
| 88 | +        client_cert(str): TLS private key file path.
 | |
| 89 | + | |
| 90 | +    Returns:
 | |
| 91 | +        (str, Channel):
 | |
| 92 | + | |
| 93 | +    Raises:
 | |
| 94 | +        InvalidArgumentError: On any input parsing error.
 | |
| 95 | +    """
 | |
| 96 | +    url = urlparse(remote_url)
 | |
| 97 | +    remote = '{}:{}'.format(url.hostname, url.port or 50051)
 | |
| 98 | + | |
| 99 | +    if url.scheme == 'http':
 | |
| 100 | +        channel = grpc.insecure_channel(remote)
 | |
| 101 | + | |
| 102 | +    elif url.scheme == 'https':
 | |
| 103 | +        credentials = load_tls_channel_credentials(client_key, client_cert, server_cert)
 | |
| 104 | +        if not credentials:
 | |
| 105 | +            raise InvalidArgumentError("Given TLS details (or defaults) could be loaded")
 | |
| 106 | + | |
| 107 | +        channel = grpc.secure_channel(remote, credentials)
 | |
| 108 | + | |
| 109 | +    else:
 | |
| 110 | +        raise InvalidArgumentError("Given remote does not specify a protocol")
 | |
| 111 | + | |
| 112 | +    if authorization_token is not None:
 | |
| 113 | +        token = load_channel_authorization_token(authorization_token)
 | |
| 114 | +        if not token:
 | |
| 115 | +            raise InvalidArgumentError("Given authorization token could be loaded")
 | |
| 116 | + | |
| 117 | +        interpector =  AuthMetadataClientInterceptor(token)
 | |
| 118 | +        channel = grpc.intercept_channel(channel, interpector)
 | |
| 119 | + | |
| 120 | +    return channel
 | |
| 121 | + | |
| 122 | + | |
| 123 | +class AuthMetadataClientInterceptor(
 | |
| 124 | +        grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor,
 | |
| 125 | +        grpc.StreamUnaryClientInterceptor, grpc.StreamStreamClientInterceptor):
 | |
| 126 | + | |
| 127 | +    def __init__(self, authorization_token=None, authorization_secret=None):
 | |
| 128 | +        """Initialises a new :class:`AuthMetadataClientInterceptor`.
 | |
| 129 | + | |
| 130 | +        Args:
 | |
| 131 | +            authorization_token (str): Authorization token as a string.
 | |
| 132 | +        """
 | |
| 133 | +        if authorization_token:
 | |
| 134 | +            self.__secret = authorization_token.strip()
 | |
| 135 | +        else:
 | |
| 136 | +            self.__secret = base64.b64encode(authorization_secret)
 | |
| 137 | + | |
| 138 | +        self.__header_field_name = 'authorization'
 | |
| 139 | +        self.__header_field_value = 'Bearer {}'.format(self.__secret)
 | |
| 140 | + | |
| 141 | +    def intercept_unary_unary(self, continuation, client_call_details, request):
 | |
| 142 | +        new_details = self._amend_call_details(client_call_details)
 | |
| 143 | + | |
| 144 | +        return continuation(new_details, request)
 | |
| 145 | + | |
| 146 | +    def intercept_unary_stream(self, continuation, client_call_details, request):
 | |
| 147 | +        new_details = self._amend_call_details(client_call_details)
 | |
| 148 | + | |
| 149 | +        return continuation(new_details, request)
 | |
| 150 | + | |
| 151 | +    def intercept_stream_unary(self, continuation, client_call_details, request_iterator):
 | |
| 152 | +        new_details = self._amend_call_details(client_call_details)
 | |
| 153 | + | |
| 154 | +        return continuation(new_details, request_iterator)
 | |
| 155 | + | |
| 156 | +    def intercept_stream_stream(self, continuation, client_call_details, request_iterator):
 | |
| 157 | +        new_details = self._amend_call_details(client_call_details)
 | |
| 158 | + | |
| 159 | +        return continuation(new_details, request_iterator)
 | |
| 160 | + | |
| 161 | +    def _amend_call_details(self, client_call_details):
 | |
| 162 | +        if client_call_details.metadata is not None:
 | |
| 163 | +            new_metadata = list(client_call_details.metadata)
 | |
| 164 | +        else:
 | |
| 165 | +            new_metadata = []
 | |
| 166 | + | |
| 167 | +        new_metadata.append((self.__header_field_name, self.__header_field_value,))
 | |
| 168 | + | |
| 169 | +        class _ClientCallDetails(
 | |
| 170 | +                namedtuple('_ClientCallDetails',
 | |
| 171 | +                           ('method', 'timeout', 'credentials', 'metadata')),
 | |
| 172 | +                grpc.ClientCallDetails):
 | |
| 173 | +            pass
 | |
| 174 | + | |
| 175 | +        return _ClientCallDetails(client_call_details.method,
 | |
| 176 | +                                  client_call_details.timeout,
 | |
| 177 | +                                  client_call_details.credentials,
 | |
| 178 | +                                  new_metadata) | 
| ... | ... | @@ -26,6 +26,7 @@ import grpc | 
| 26 | 26 |  from buildgrid._enums import BotStatus, MetricRecordDomain, MetricRecordType
 | 
| 27 | 27 |  from buildgrid._protos.buildgrid.v2 import monitoring_pb2
 | 
| 28 | 28 |  from buildgrid.server.actioncache.service import ActionCacheService
 | 
| 29 | +from buildgrid.server._authentication import JwtAlgorithm, AuthMetadataServerInterceptor
 | |
| 29 | 30 |  from buildgrid.server.bots.service import BotsService
 | 
| 30 | 31 |  from buildgrid.server.cas.service import ByteStreamService, ContentAddressableStorageService
 | 
| 31 | 32 |  from buildgrid.server.execution.service import ExecutionService
 | 
| ... | ... | @@ -57,7 +58,9 @@ class BuildGridServer: | 
| 57 | 58 |              max_workers = (os.cpu_count() or 1) * 5
 | 
| 58 | 59 |  | 
| 59 | 60 |          self.__grpc_executor = futures.ThreadPoolExecutor(max_workers)
 | 
| 60 | -        self.__grpc_server = grpc.server(self.__grpc_executor)
 | |
| 61 | +        self.__grpc_auth_interceptor = AuthMetadataServerInterceptor('your-256-bit-secret', JwtAlgorithm.HS256)
 | |
| 62 | +        self.__grpc_server = grpc.server(
 | |
| 63 | +            self.__grpc_executor, interceptors=(self.__grpc_auth_interceptor,))
 | |
| 61 | 64 |  | 
| 62 | 65 |          self.__main_loop = asyncio.get_event_loop()
 | 
| 63 | 66 |          self.__monitoring_bus = None
 | 
