Skip to content

Replace fs-watch based TLS with new Identity service#213

Merged
olix0r merged 64 commits intomasterfrom
ver/identity-client-stack
Mar 16, 2019
Merged

Replace fs-watch based TLS with new Identity service#213
olix0r merged 64 commits intomasterfrom
ver/identity-client-stack

Conversation

@olix0r
Copy link
Member

@olix0r olix0r commented Mar 14, 2019

This change replaces the proxy's TLS configuration logic so that certificates
are dynamically provisioned by the proxy via a new Identity controller service.

When the proxy processes its configuration, it now requires that a
LINKERD2_PROXY_IDENTITY_DISABLED environment is set; oterhwise,
a valid identity configuration must be provided. An identity configuration
includes:

  • PEM-encoded trust anchors provided via the
    LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS environment;
  • A directory, specified by LINKERD2_PROXY_IDENTITY_DIR, containing two
    files: key.p8 and csr.der. The key includes a PKCS#8-encoded private
    key; and the CSR includes a certificate request, signed by the private key,
    that can be sent to the identity service;
  • A serviceaccount token specified by LINKERD2_PROXY_IDENTITY_TOKEN_FILE
    that the proxy will read each time it requests a new certificate.

The linkerd2_fs_watch crate is no longer used and should probably be split
out into a utility (perhaps tokio-fs?).

Various other configuration options have changed for consistency:

  • URLs are no longer accepted when we only care about addresses.
  • Configuration specific to the destination service (as opposed to all
    controller clients) has been renamed appropriately.

Once the configuration is processed---if Identity is not completely
disabled---then a client is established to the identity service and its
identity is validated. As a certificate is acquired, it is validated and
published into the listeners and connect stacks (rather than into the
routing stack). This removes the need for rebinding watch layers, since these
connection layers lazily acquire the proper configuration.

When a certificate has not yet been acquired, then ALL inbound TLS
connections that should be terminated by the process fail, and all outbound
connections are server-validated but no client identity is provided. Once the
certificate is acquired, client identities are provided on subsequent
connections and the listener will properly accept connections.


Depends on linkerd/linkerd2-proxy-api#25

olix0r and others added 30 commits February 14, 2019 17:09
Previously, the proxy accepted an identity template and namespace and
merged them during configuration. We can rely on external configuration
systems to do this templating, and so we simplify the proxy by no longer
taking a pod namespace environment and instead taking a
`LINKERD2_PROXY_LOCAL_IDENTITY`.
@olix0r olix0r requested review from adleong, hawkw and seanmonstar March 14, 2019 21:07
Dockerfile Outdated

ARG RUST_IMAGE=rust:1.32.0
ARG RUNTIME_IMAGE=gcr.io/linkerd-io/base:2017-10-30.01
ARG RUNTIME_IMAGE=gcr.io/linkerd-io/proxy:git-820f9cd8
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this dockerfile now uses a proxy image so that we can just overwrite the binary. This is now necessary because we require utilities built in the other repo.

@olix0r
Copy link
Member Author

olix0r commented Mar 14, 2019

I should note that three tests are now disabled... two of which require a mock identity service implementation for integration tests

Copy link
Contributor

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diff is pretty hard to review because it's quite large and a lot of code was moved around. However, it seems good to me.

I commented on a number of nits that are generally not blockers (with the exception of removing the #![allow(...)]` attribute which we should definitely do prior to merging).

// ===== impl Config =====

impl Config {
impl dns::ConfigureResolver for Config {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the change to traits for configuration --- I suspect this will make writing tests for certain configurations much more pleasant.


let metrics_retain_idle = parse(strings, ENV_METRICS_RETAIN_IDLE, parse_duration);

let dns_min_ttl = parse(strings, ENV_DNS_MIN_TTL, parse_duration);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is relatively minor, but I notice that we've basically namespaced config values in the Config structs by prepending the thing we're configuring to the name; i.e. dns_{min_ttl, max_ttl, canonicalize_timeout} and so on. I wonder if it's worthwhile to change this so that Config is composed of sub-structs, like

struct Config {
    dns_config: DnsConfig, 
    ...
}
struct DnsConfig {
    min_ttl: Duration,
    max_ttl: Duration,
    canonicalize_timeout: Duration,
    ...
}

// and so on

This might make the places where the config is actually used a bit simpler --- I've noticed that sometimes it's necessary to borrow the entire config struct to access one field, making the code in main a bit more convoluted than it needs to be; similarly we have several functions that take a number of related config fields as individual arguments to avoid borrowing the whole Config structure. Grouping related config fields into structs might make this code a little less painful, as we could (for example) just pass &config.dns_config into the function that constructs a DNS resolver, and so on.

This is admittedly not high priority. I'm only bringing it up now because I've put it off for a while to avoid changing config.rs, and I thought that as this branch is making a lot of major changes to the config module, we might want to consider some refactoring.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this is a good general direction -- each app module should have its own config struct, and then the main config i sresponsible for parsing/validating them.


let disabled = strings
.get(ENV_IDENTITY_DISABLED)?
.map(|d| !d.is_empty())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how I feel about the implication that LINKERD_PROXY_IDENTITY_DISABLED=false will disable identity, but this isn't user facing so 🤷‍♀️

li?,
tok?,
min_refresh?,
max_refresh?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It's possible that this match is the clearest way to represent this logic, but I find it a little hard to follow. Can't really come up with a better way to do it though...

min_refresh,
max_refresh,
) => {
let key = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIOLI: maybe the common logic for parsing the key and the CSR could be factored out into a function? Not a huge deal.

@olix0r olix0r merged commit 866b1f6 into master Mar 16, 2019
@olix0r olix0r deleted the ver/identity-client-stack branch March 16, 2019 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants