Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix secret handling #1153

Merged
merged 5 commits into from
Jan 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ RUN releng/install-py.sh prd install */requirements.txt
RUN rm -rf ./multi ./ambassador

# Grab kubewatch
RUN wget -q https://s3.amazonaws.com/datawire-static-files/kubewatch/0.3.13/$(go env GOOS)/$(go env GOARCH)/kubewatch
RUN wget -q https://s3.amazonaws.com/datawire-static-files/kubewatch/0.3.15/$(go env GOOS)/$(go env GOARCH)/kubewatch
RUN chmod +x kubewatch

# Clean up no-longer-needed dev stuff.
Expand Down Expand Up @@ -109,7 +109,8 @@ RUN chgrp -R 0 ${AMBASSADOR_ROOT} && \
# COPY the entrypoint and Python-kubewatch and make them runnable.
COPY ambassador/kubewatch.py .
COPY ambassador/entrypoint.sh .
RUN chmod 755 kubewatch.py entrypoint.sh
COPY ambassador/post_update.py .
RUN chmod 755 kubewatch.py entrypoint.sh post_update.py

# Grab ambex, too.
RUN wget -q https://s3.amazonaws.com/datawire-static-files/ambex/0.1.1/ambex
Expand Down
14 changes: 7 additions & 7 deletions ambassador/ambassador/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from .envoy import EnvoyConfig, V1Config, V2Config
from .ir.irtlscontext import IRTLSContext

from .utils import RichStatus, SavedSecret, SplitConfigChecker
from .utils import RichStatus, SavedSecret, SecretSaver

__version__ = Version

Expand Down Expand Up @@ -104,10 +104,12 @@ def file_checker(path: str) -> bool:
return True


def cli_secret_reader(context: IRTLSContext, secret_name: str, namespace: str, secret_root: str) -> SavedSecret:
def cli_secret_reader(context: IRTLSContext, secret_name: str, namespace: str) -> SavedSecret:
# In the Real World, the secret reader should, y'know, read secrets..
# Here we're just gonna fake it.

secret_root = os.environ.get('AMBASSADOR_CONFIG_BASE_DIR', "/ambassador")

cert_path = os.path.join(secret_root, namespace, "cli-secrets", secret_name, "tls.crt")
key_path = os.path.join(secret_root, namespace, "cli-secrets", secret_name, "tls.key")

Expand Down Expand Up @@ -371,7 +373,7 @@ def splitconfig(root_path: Parameter.REQUIRED, *, ambex_pid: int=0,

# root_path contains directories for each resource type: services, secrets, optional
# crd-whatever paths.
scc = SplitConfigChecker(logger, root_path)
scc = SecretSaver(logger, root_path, root_path)

# Start by assuming that we're going to look at everything.
config_root = root_path
Expand All @@ -384,10 +386,8 @@ def splitconfig(root_path: Parameter.REQUIRED, *, ambex_pid: int=0,
aconf = Config()
aconf.load_from_directory(config_root, k8s=k8s, recurse=True)

# Use the SplitConfigChecker to resolve secrets. We don't pass a file checker
# because anything in the config using an actual path needs to be passing a
# correct path by this point.
ir = IR(aconf, secret_reader=scc.secret_reader)
# Use the SecretSaver to read secret from the filesystem.
ir = IR(aconf, secret_reader=scc.file_reader)

# Generate a V2Config from that, and grab the split bootstrap and ADS configs.
v2config = V2Config(ir)
Expand Down
6 changes: 6 additions & 0 deletions ambassador/ambassador/diagnostics/envoy_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ def cluster_stats(self, name):
return cstat

def update_log_levels(self, last_attempt, level=None):
# logging.info("updating levels")

try:
url = "http://127.0.0.1:8001/logging"

Expand Down Expand Up @@ -182,6 +184,8 @@ def update_log_levels(self, last_attempt, level=None):
return True

def update_envoy_stats(self, last_attempt):
# logging.info("updating stats")

try:
r = requests.get("http://127.0.0.1:8001/stats")
except OSError as e:
Expand Down Expand Up @@ -297,6 +301,8 @@ def update_envoy_stats(self, last_attempt):
"envoy": envoy_stats
})

# logging.info("stats updated")

# def update(self, active_mapping_names):
def update(self):
try:
Expand Down
42 changes: 27 additions & 15 deletions ambassador/ambassador/envoy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,28 @@
# See the License for the specific language governing permissions and
# limitations under the License

from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Tuple

import json

from abc import abstractmethod

from ..ir import IR, IRResource
from ..ir.irmapping import IRMappingGroup

def sanitize_pre_json(input):
# Removes all potential null values
if isinstance(input, dict):
for key, value in list(input.items()):
if value is None:
del input[key]
else:
sanitize_pre_json(value)
elif isinstance(input, list):
for item in input:
sanitize_pre_json(item)
return input

class EnvoyConfig:
"""
Base class for Envoy configuration that permits fetching configuration
Expand Down Expand Up @@ -49,6 +64,17 @@ def save_element(self, kind: str, resource: IRResource, obj: Any):
self.add_element(kind, resource.location, obj)
return obj

@abstractmethod
def split_config(self) -> Tuple[Dict[str, Any], Dict[str, Any]]:
pass

@abstractmethod
def as_dict(self) -> Dict[str, Any]:
pass

def as_json(self):
return json.dumps(sanitize_pre_json(self.as_dict()), sort_keys=True, indent=4)

@classmethod
def generate(cls, ir: IR, version: str="V2") -> 'EnvoyConfig':
if version == "V1":
Expand All @@ -70,17 +96,3 @@ def _get_envoy_route(self, group: IRMappingGroup) -> str:
return self.regex
else:
return self.prefix


def sanitize_pre_json(input):
# Removes all potential null values
if isinstance(input, dict):
for key, value in list(input.items()):
if value is None:
del input[key]
else:
sanitize_pre_json(value)
elif isinstance(input, list):
for item in input:
sanitize_pre_json(item)
return input
11 changes: 4 additions & 7 deletions ambassador/ambassador/envoy/v1/v1config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License

from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union

import json
import logging
from typing import Any, Dict, List, Optional, Tuple

from ...ir import IR
from ..common import EnvoyConfig
Expand Down Expand Up @@ -55,7 +52,7 @@ def __init__(self, ir: IR) -> None:
V1Tracing.generate(self)
V1GRPCService.generate(self)

def as_dict(self):
def as_dict(self) -> Dict[str, Any]:
d = {
'admin': self.admin,
'listeners': self.listeners,
Expand All @@ -74,5 +71,5 @@ def as_dict(self):

return d

def as_json(self):
return json.dumps(self.as_dict(), sort_keys=True, indent=4)
def split_config(self) -> Tuple[Dict[str, Any], Dict[str, Any]]:
raise NotImplementedError
7 changes: 2 additions & 5 deletions ambassador/ambassador/envoy/v2/v2config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,15 @@ def __init__(self, ir: IR) -> None:
V2StaticResources.generate(self)
V2Bootstrap.generate(self)

def as_dict(self):
def as_dict(self) -> Dict[str, Any]:
d = {
'bootstrap': self.bootstrap,
'static_resources': self.static_resources
}

return d

def as_json(self):
return json.dumps(sanitize_pre_json(self.as_dict()), sort_keys=True, indent=4)

def split_config(self) -> tuple:
def split_config(self) -> Tuple[Dict[str, Any], Dict[str, Any]]:
ads_config = {
'@type': '/envoy.config.bootstrap.v2.Bootstrap',
'static_resources': self.static_resources
Expand Down
6 changes: 2 additions & 4 deletions ambassador/ambassador/ir/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ def __init__(self, aconf: Config, secret_reader=None, file_checker=None) -> None
self.logger = logging.getLogger("ambassador.ir")

# We're using setattr since since mypy complains about assigning directly to a method.
setattr(self, 'secret_reader', secret_reader or KubeSecretReader())
secret_root = os.environ.get('AMBASSADOR_CONFIG_BASE_DIR', "/ambassador")
setattr(self, 'secret_reader', secret_reader or KubeSecretReader(secret_root))
setattr(self, 'file_checker', file_checker if file_checker is not None else os.path.isfile)

# OK. Remember the root of the secret store...
self.secret_root = os.environ.get('AMBASSADOR_CONFIG_BASE_DIR', "/ambassador")

self.logger.debug("IR __init__:")
self.logger.debug("IR: Version %s built from %s on %s" % (Version, Build.git.commit, Build.git.branch))
self.logger.debug("IR: AMBASSADOR_ID %s" % self.ambassador_id)
Expand Down
2 changes: 1 addition & 1 deletion ambassador/ambassador/ir/irtlscontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def load_secret(self, secret_name: str) -> SavedSecret:
secret_name, namespace = secret_name.split('.', 1)

sr = getattr(self.ir, 'secret_reader')
return sr(self, secret_name, namespace, self.ir.secret_root)
return sr(self, secret_name, namespace)

def resolve(self) -> bool:
# is_valid determines if the TLS context is valid
Expand Down
Loading