Federation tutorial (#29)
* Added example apps code, SPIRE configurations, docker files, and shell scripts. Signed-off-by: martincapello <m.a.capello@gmail.com> * Removed commented out config Signed-off-by: martincapello <m.a.capello@gmail.com> * Added readme. Signed-off-by: martincapello <m.a.capello@gmail.com> * Added how to use federation with WebPKI authentication. Signed-off-by: martincapello <m.a.capello@gmail.com> * Addressed PR comments Signed-off-by: martincapello <m.a.capello@gmail.com> * Added Federation to the main README.md Signed-off-by: martincapello <m.a.capello@gmail.com> * Addressed PR comments. Signed-off-by: martincapello <m.a.capello@gmail.com> * Address PR comments. Signed-off-by: martincapello <m.a.capello@gmail.com> * Addressed PR comments. Signed-off-by: martincapello <m.a.capello@gmail.com> * Added newline to the end of the file. Signed-off-by: martincapello <m.a.capello@gmail.com> * Removed blank spaces in last line Signed-off-by: martincapello <m.a.capello@gmail.com> * Addressed PR comments. Signed-off-by: martincapello <m.a.capello@gmail.com> * Addressed PR comments. Signed-off-by: martincapello <m.a.capello@gmail.com>
This commit is contained in:
parent
44be01528e
commit
28be9cb9c3
|
|
@ -9,6 +9,7 @@ The tutorials in this repo describe how to install SPIRE and integrate it with s
|
||||||
| [Integrating with Envoy using X.509 certs](k8s/envoy-x509) | Kubernetes |
|
| [Integrating with Envoy using X.509 certs](k8s/envoy-x509) | Kubernetes |
|
||||||
| [Integrating with Envoy using JWT](k8s/envoy-jwt) | Kubernetes |
|
| [Integrating with Envoy using JWT](k8s/envoy-jwt) | Kubernetes |
|
||||||
| [Nested SPIRE](nested-spire) | Docker Compose |
|
| [Nested SPIRE](nested-spire) | Docker Compose |
|
||||||
|
| [Federation](federation) | Docker Compose |
|
||||||
|
|
||||||
Additional examples of how to install and deploy SPIRE are available. The spiffe.io [Try SPIRE](https://spiffe.io/spire/try/) page includes a [Quickstart for Linux and MacOS X](https://spiffe.io/spire/try/getting-started-linux-macos-x/) and [SPIFFE Library Usage Examples](https://spiffe.io/spire/try/spiffe-library-usage-examples/). The [SPIRE Examples](https://github.com/spiffe/spire-examples) repo on GitHub includes more usage examples for Kubernetes deployments, including Postgres integration, and a Docker-based Envoy example.
|
Additional examples of how to install and deploy SPIRE are available. The spiffe.io [Try SPIRE](https://spiffe.io/spire/try/) page includes a [Quickstart for Linux and MacOS X](https://spiffe.io/spire/try/getting-started-linux-macos-x/) and [SPIFFE Library Usage Examples](https://spiffe.io/spire/try/spiffe-library-usage-examples/). The [SPIRE Examples](https://github.com/spiffe/spire-examples) repo on GitHub includes more usage examples for Kubernetes deployments, including Postgres integration, and a Docker-based Envoy example.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
bb=$(tput bold)
|
||||||
|
nn=$(tput sgr0)
|
||||||
|
|
||||||
|
# Bootstrap trust to the SPIRE server for each agent by copying over the
|
||||||
|
# trust bundle into each agent container.
|
||||||
|
echo "${bb}Bootstrapping trust between SPIRE agents and SPIRE servers...${nn}"
|
||||||
|
docker-compose exec -T spire-server-broker bin/spire-server bundle show |
|
||||||
|
docker-compose exec -T broker-webapp tee conf/agent/bootstrap.crt > /dev/null
|
||||||
|
docker-compose exec -T spire-server-stock bin/spire-server bundle show |
|
||||||
|
docker-compose exec -T stock-quotes-service tee conf/agent/bootstrap.crt > /dev/null
|
||||||
|
|
||||||
|
# Start up the broker-webapp SPIRE agent.
|
||||||
|
echo "${bb}Starting broker-webapp SPIRE agent...${nn}"
|
||||||
|
docker-compose exec -d broker-webapp bin/spire-agent run
|
||||||
|
|
||||||
|
# Start up the stock-quotes-service SPIRE agent.
|
||||||
|
echo "${bb}Starting stock-quotes-service SPIRE agent...${nn}"
|
||||||
|
docker-compose exec -d stock-quotes-service bin/spire-agent run
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
bb=$(tput bold)
|
||||||
|
nn=$(tput sgr0)
|
||||||
|
|
||||||
|
echo "${bb}bootstrapping bundle from broker to quotes-service server...${nn}"
|
||||||
|
docker-compose exec -T spire-server-broker \
|
||||||
|
/opt/spire/bin/spire-server bundle show -format spiffe > docker/spire-server-stockmarket.example/conf/broker.example.bundle
|
||||||
|
docker-compose exec -T spire-server-stock \
|
||||||
|
/opt/spire/bin/spire-server bundle set -format spiffe -id spiffe://broker.example -path /opt/spire/conf/server/broker.example.bundle
|
||||||
|
|
||||||
|
echo "${bb}bootstrapping bundle from quotes-service to broker server...${nn}"
|
||||||
|
docker-compose exec -T spire-server-stock \
|
||||||
|
/opt/spire/bin/spire-server bundle show -format spiffe > docker/spire-server-broker.example/conf/stockmarket.example.bundle
|
||||||
|
docker-compose exec -T spire-server-broker \
|
||||||
|
/opt/spire/bin/spire-server bundle set -format spiffe -id spiffe://stockmarket.example -path /opt/spire/conf/server/stockmarket.example.bundle
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
bb=$(tput bold)
|
||||||
|
nn=$(tput sgr0)
|
||||||
|
|
||||||
|
fingerprint() {
|
||||||
|
# calculate the SHA1 digest of the DER bytes of the certificate using the
|
||||||
|
# "coreutils" output format (`-r`) to provide uniform output from
|
||||||
|
# `openssl sha1` on macOS and linux.
|
||||||
|
cat $1 | openssl x509 -outform DER | openssl sha1 -r | awk '{print $1}'
|
||||||
|
}
|
||||||
|
|
||||||
|
BROKER_WEBAPP_AGENT_FINGERPRINT=$(fingerprint docker/broker-webapp/conf/agent.crt.pem)
|
||||||
|
QUOTES_SERVICE_AGENT_FINGERPRINT=$(fingerprint docker/stock-quotes-service/conf/agent.crt.pem)
|
||||||
|
|
||||||
|
echo "${bb}Creating registration entry for the broker-webapp...${nn}"
|
||||||
|
docker-compose exec spire-server-broker bin/spire-server entry create \
|
||||||
|
-parentID spiffe://broker.example/spire/agent/x509pop/${BROKER_WEBAPP_AGENT_FINGERPRINT} \
|
||||||
|
-spiffeID spiffe://broker.example/webapp \
|
||||||
|
-selector unix:user:root \
|
||||||
|
-federatesWith "spiffe://stockmarket.example"
|
||||||
|
|
||||||
|
echo "${bb}Creating registration entry for the stock-quotes-service...${nn}"
|
||||||
|
docker-compose exec spire-server-stock bin/spire-server entry create \
|
||||||
|
-parentID spiffe://stockmarket.example/spire/agent/x509pop/${QUOTES_SERVICE_AGENT_FINGERPRINT} \
|
||||||
|
-spiffeID spiffe://stockmarket.example/quotes-service \
|
||||||
|
-selector unix:user:root \
|
||||||
|
-federatesWith "spiffe://broker.example"
|
||||||
|
|
@ -0,0 +1,501 @@
|
||||||
|
|
||||||
|
# Using SPIFFE Federation to Authenticate Workloads from Different SPIRE Servers
|
||||||
|
|
||||||
|
This tutorial shows how to authenticate two SPIFFE-identified workloads that are identified by two different SPIRE Servers.
|
||||||
|
|
||||||
|
The first part of this document demonstrates how to configure SPIFFE federation by showing the SPIRE configuration file changes and `spire-server` commands used to set up a stock quote webapp frontend and service backend. The second part of this document lists the steps you run to show the scenario in action using the Docker Compose files included in this tutorial's directory.
|
||||||
|
|
||||||
|
In this tutorial you will learn how to:
|
||||||
|
|
||||||
|
* Configure each SPIRE Server to expose its SPIFFE Federation bundle endpoint using SPIFFE authentication and Web PKI authentication.
|
||||||
|
* Configure the SPIRE Servers to retrieve trust bundles from each other.
|
||||||
|
* Bootstrap federation between two SPIRE Servers using different trust domains.
|
||||||
|
* Create registration entries for the workloads so that they can federate with other trust domain.
|
||||||
|
|
||||||
|
# Prerequisites
|
||||||
|
|
||||||
|
The baseline components for SPIFFE federation are:
|
||||||
|
|
||||||
|
* Two SPIRE Server instances
|
||||||
|
* Two SPIRE Agents, one connected to one SPIRE Server, and the second connected to the other SPIRE Server
|
||||||
|
* Two workloads that needs to communicate each other via mTLS, and use the Workload API to get SVIDs and trust bundles
|
||||||
|
|
||||||
|
# Scenario
|
||||||
|
|
||||||
|
Let's say we have a stock broker's webapp that wants to display stock quotes fetched from a stock market web service provider. The scenario goes as follows:
|
||||||
|
|
||||||
|
1. The user enters the broker's webapp stock quotes URL in a browser.
|
||||||
|
2. The webapp workload receives the request and makes an HTTP request for quotes to the stock market service using mTLS.
|
||||||
|
3. The stock market service receives the request and sends the quotes in the response.
|
||||||
|
4. The webapp renders the stock quotes page using the returned quotes and sends it to the browser.
|
||||||
|
5. The browser displays the quotes to the user. The webapp includes some JavaScript to refresh the page every 1 second, so every second these steps are executed again.
|
||||||
|
|
||||||
|
In addition to the above and for the rest of this tutorial, we are going to assume the following [trust domain](https://spiffe.io/docs/latest/spiffe/concepts/#trust-domain) names for these sample SPIRE installations: `broker.example` and `stockmarket.example`. Keep in mind that trust domains do not need to correspond to actual DNS domain names. Also, the applications access the WorkloadAPI directly to get SVIDs and trust bundles, meaning there are no proxies in the scenario described.
|
||||||
|
|
||||||
|
# Configure SPIFFE Federation Endpoints
|
||||||
|
|
||||||
|
To make federation work, and because the webapp and the quotes service are going to use `mTLS`, both SPIRE Servers need each other's trust bundle. This is done, in part, by configuring a so-called federation endpoint on each SPIRE Server, which provides the API used by SPIRE Servers in other trust domains to get the trust bundle for the trust domain they want to federate with.
|
||||||
|
|
||||||
|
The federation endpoint exposed by a SPIRE Server can be configured to use one of two authentication methods: SPIFFE auth or Web PKI auth.
|
||||||
|
|
||||||
|
## Configure a Federation Endpoint Using SPIFFE Authentication
|
||||||
|
|
||||||
|
To configure the broker's SPIRE Server bundle endpoint, we use the `federation` section in the broker's SPIRE Server configuration file, by default `server.conf`:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
server {
|
||||||
|
.
|
||||||
|
.
|
||||||
|
trust_domain = "broker.example"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This will publish the federation endpoint in any IP address at port 8443 in the host where the SPIRE Server is running.
|
||||||
|
|
||||||
|
On the other side, the stock market service provider's SPIRE Server is configured in a similar fashion:
|
||||||
|
```hcl
|
||||||
|
server {
|
||||||
|
.
|
||||||
|
.
|
||||||
|
trust_domain = "stockmarket.example"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point, both SPIRE Servers have exposed their federation endpoints to provide their trust bundles, but none of them knows how to reach each other's federation endpoint.
|
||||||
|
|
||||||
|
## Configure a Federation Endpoint Using Web PKI Authentication
|
||||||
|
|
||||||
|
We are going to assume that only the broker's SPIRE Server will use Web PKI authentication for its federation endpoint. The stock market SPIRE Server will still use SPIFFE authentication. Hence, the stock market SPIRE Server configuration remains the same as seen in the previous section.
|
||||||
|
|
||||||
|
Then, to configure the broker's SPIRE Server bundle endpoint, we configure the `federation` section as follows:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
server {
|
||||||
|
.
|
||||||
|
.
|
||||||
|
trust_domain = "broker.example"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 443
|
||||||
|
acme {
|
||||||
|
domain_name = "broker.example"
|
||||||
|
email = "some@email.com"
|
||||||
|
tos_accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This will publish the federation endpoint in any IP address at port 443. We use port 443 because we're demonstrating the use of Let's Encrypt as our ACME provider (this is used by default, if you want to use a different one then you must set the `directory_url` configurable). Note that `tos_accepted` was set to `true`, meaning that we accept the terms of service of our ACME provider, which in turn is needed when using Let's Encrypt.
|
||||||
|
|
||||||
|
For SPIFFE Federation using Web PKI to work, you must own the DNS domain specified for `domain_name` (`broker.example` in our example) and the domain must resolve to the SPIRE Server exposing the federation bundle endpoint.
|
||||||
|
|
||||||
|
# Configure SPIRE Servers to Retrieve Trust Bundles From Each Other
|
||||||
|
|
||||||
|
After configuring federation endpoints, the next step to enable SPIFFE federation is to configure the SPIRE Servers to find the trust bundles for other trust domains. The `federates_with` configuration option in `server.conf` is where you specify the endpoint of the other trust domain. The configuration of this section has some slight differences when using the different methods of authentication.
|
||||||
|
|
||||||
|
## Configure Trust Bundle Location Using SPIFFE Authentication
|
||||||
|
|
||||||
|
As we saw previously, the SPIRE Server of the stock market service provider has its federation endpoint listening on port `8443` at any IP address. We will also assume that `spire-server-stock` is a DNS name that resolves to the stock market service's SPIRE Server IP address. (The Docker Compose demo here uses the hostname `spire-server-stock`, but in typical usage you would specify a FQDN.) Then, the broker's SPIRE Server must be configured with the following `federates_with` section:
|
||||||
|
```hcl
|
||||||
|
server {
|
||||||
|
.
|
||||||
|
.
|
||||||
|
trust_domain = "broker.example"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "stockmarket.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "spire-server-stock"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Now the broker's SPIRE Server knows where to find a trust bundle that can be used to validate SVIDs containing identities from the `stockmarket.example` trust domain.
|
||||||
|
|
||||||
|
On the other side, the stock market service provider's SPIRE Server must be configured in a similar fashion:
|
||||||
|
```hcl
|
||||||
|
server {
|
||||||
|
.
|
||||||
|
.
|
||||||
|
trust_domain = "stockmarket.example"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "broker.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "spire-server-broker"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
That is it. Specifying the `federation` section and `federates_with` subsection of `server.conf` is all that's needed configure SPIFFE federation. To finish enabling SPIFFE federation, we need to bootstrap the trust bundles and register the workloads using `spire-server` commands as described below.
|
||||||
|
|
||||||
|
## Configure Trust Bundle Location Using Web PKI authentication
|
||||||
|
|
||||||
|
As mentioned, in this alternate scenario we are assuming that only the broker's SPIRE Server will use Web PKI authentication for its federation endpoint, so the `federates_with` configuration for the broker server is the same as seen in the previous section. However, the SPIRE Server of the stock market service provider needs a different configuration:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
server {
|
||||||
|
.
|
||||||
|
.
|
||||||
|
trust_domain = "stockmarket.example"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "broker.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "broker.example"
|
||||||
|
use_web_pki = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The differences are:
|
||||||
|
- `port` was removed. This is because by default it is set to `443`, which is the port where the broker's federation bundle endpoint is listening.
|
||||||
|
- `address` now is set to the broker's domain `broker.example`.
|
||||||
|
- `use_web_pki` was added and set to `true`. This is mandatory when the bundle endpoint to which we want to federate is using Web PKI authentication.
|
||||||
|
|
||||||
|
# Bootstrap Federation
|
||||||
|
|
||||||
|
We have configured the SPIRE Servers with the address of the federation endpoints, but this is not enough to make federation work. To enable the SPIRE Servers to fetch the trust bundles from each other they need each other's trust bundle first, because they have to authenticate the SPIFFE identity of the federated server that is trying to access the federation endpoint. Once federation is bootstrapped, the trust bundle updates are fetched trough the federation endpoint API using the current trust bundle.
|
||||||
|
|
||||||
|
The bootstrapping is done using a couple of SPIRE Server commands: `bundle show` and `bundle set`.
|
||||||
|
|
||||||
|
## Get the Bootstrap Trust Bundle
|
||||||
|
|
||||||
|
Let's say we want to get the broker's SPIRE Server trust bundle. On the node where the broker's SPIRE Server is running we run:
|
||||||
|
|
||||||
|
```
|
||||||
|
broker> spire-server bundle show -format spiffe > broker.example.bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
This saves the trust bundle in the `broker.example.bundle` file. Then the broker must give a copy of this file to the stock market service folks, so they can store this trust bundle on their SPIRE Server and associate it with the `broker.example` trust domain. To achieve this, the stock market service folks must run the following on the node where they have SPIRE Server running:
|
||||||
|
|
||||||
|
```
|
||||||
|
stock-market> spire-server bundle set -format spiffe -id spiffe://broker.example -path /some/path/broker.example.bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point the stock market service's SPIRE Server is able to validate SVIDs having SPIFFE IDs with a `broker.example` trust domain. However, the broker's SPIRE Server is not yet able to validate SVIDs having SPIFFE IDs with a `stockmarket.example` trust domain. To make this possible, the stock market folks must run the following on the node where they have SPIRE Server running:
|
||||||
|
|
||||||
|
```
|
||||||
|
stock-market> spire-server bundle show -format spiffe > stockmarket.example.bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the stock market folks must give a copy of this file to the broker folks, so they can store this trust bundle on their SPIRE Server and associate it with the `stockmarket.example` trust domain. To achieve this, the broker folks must run the following on the node where they have SPIRE Server running:
|
||||||
|
|
||||||
|
```
|
||||||
|
broker> spire-server bundle set -format spiffe -id spiffe://stockmarket.example -path /some/path/stockmarket.example.bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
Now both SPIRE Servers can validate SVIDs having SPIFFE IDs with each other's trust domain, thus both can start fetching trust bundle updates from each other's federation endpoints. Also, as of now they can create registration entries for federating as shown in the next section.
|
||||||
|
|
||||||
|
Note that the creation of the `broker.example.bundle` file (and later importing by the stock market service) is not needed when the broker's SPIRE Server is using Web PKI authentication for its federation bundle endpoint.
|
||||||
|
|
||||||
|
# Create Registration Entries for Federation
|
||||||
|
|
||||||
|
Now that the SPIRE Servers have each other's trust bundle, let's see how they can create registration entries to federate with each other.
|
||||||
|
|
||||||
|
To simplify things, we are going to suppose that the stock market webapp and the quotes service are both running on Linux boxes, one owned by the stock market organization and the other owned by the broker. Since they are using SPIRE, each Linux box also has a SPIRE Agent installed. In addition to this, the webapp is run using the `webapp` user, and the quotes service is run using the `quotes-service` user.
|
||||||
|
|
||||||
|
With those assumptions, in the SPIRE Server node of the broker, the broker folks must create a registration entry. The `-federatesWith` flag is required to enable SPIFFE federation:
|
||||||
|
|
||||||
|
```
|
||||||
|
broker> spire-server entry create \
|
||||||
|
-parentID <SPIRE Agent's SPIFFE ID> \
|
||||||
|
-spiffeID spiffe://broker.example/webapp \
|
||||||
|
-selector unix:user:webapp \
|
||||||
|
-federatesWith "spiffe://stockmarket.example"
|
||||||
|
```
|
||||||
|
|
||||||
|
By specifying the `-federatesWith` flag, once this registration entry is created, when the webapp's SPIRE Server asks for an SVID it will get one from the broker's SPIRE Server with the `spiffe://broker.example/webapp` identity, along with the trust bundle associated to the `stockmarket.example` trust domain.
|
||||||
|
|
||||||
|
On the stock market service side, they must create a registration entry as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
stock-market> spire-server entry create \
|
||||||
|
-parentID <SPIRE Agent's SPIFFE ID> \
|
||||||
|
-spiffeID spiffe://stockmarket.example/quotes-service \
|
||||||
|
-selector unix:user:quotes-service \
|
||||||
|
-federatesWith "spiffe://broker.example"
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, once this registration entry is created, when the quotes service asks for an SVID it will get one having the `spiffe://stockmarket.example/quotes-service` identity, along with the trust bundle associated to the `broker.example` trust domain.
|
||||||
|
|
||||||
|
That is about it. Now all the pieces are in place to make federation work and demonstrate how the webapp is able to communicate with the quotes service despite having identities with different trust domains.
|
||||||
|
|
||||||
|
# Federation Example Using SPIFFE Authentication with SPIRE 0.11.0
|
||||||
|
|
||||||
|
This section explains how to use Docker Compose to try an example implementation of the SPIFFE auth scenario described in this tutorial.
|
||||||
|
|
||||||
|
Although not shown here, you could make the changes shown in the Web PKI authentication sections to try the Web PKI scenario. Remember that to configure Web PKI, the FQDN specified for `domain_name` must be owned by you and resolvable over the internet via DNS.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Required files for this tutorial can be found in the `federation` directory in https://github.com/spiffe/spire-tutorials. If you didn't already clone the repository please do so now.
|
||||||
|
|
||||||
|
Before proceeding, review the following system requirements:
|
||||||
|
- A 64-bit Linux or macOS environment
|
||||||
|
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed (Docker Compose is included in macOS Docker Desktop)
|
||||||
|
- [Go](https://golang.org/dl/) 1.14.4 or higher installed
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Ensure that the current working directory is `.../spire-tutorials/federation` and run the following command to create the files needed for Docker Compose:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Run the following command to start the SPIRE Servers and the applications:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start SPIRE Agents
|
||||||
|
|
||||||
|
Run the following command to start the SPIRE Agents:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./1-start-spire-agents.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bootstrap Federation
|
||||||
|
|
||||||
|
Run the following command to [bootstrap the federation](#bootstrap-federation):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./2-bootstrap-federation.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create Workload Registration Entries
|
||||||
|
|
||||||
|
Run the following command to create [workload registration entries](#create-registration-entries-for-federation):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./3-create-registration-entries.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
After running this script, it may take some seconds for the applications to receive their SVIDs and trust bundles.
|
||||||
|
|
||||||
|
## See the Scenario Working In a Browser
|
||||||
|
|
||||||
|
Open up a browser to http://localhost:8080/quotes and you should see a grid of randomly generated phony stock quotes that are updated every 1 second.
|
||||||
|
|
||||||
|
## See the Configuration
|
||||||
|
|
||||||
|
To see the broker's SPIRE Server configuration you can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-compose exec spire-server-broker cat conf/server/server.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
bind_address = "0.0.0.0"
|
||||||
|
bind_port = "8081"
|
||||||
|
registration_uds_path = "/tmp/spire-registration.sock"
|
||||||
|
trust_domain = "broker.example"
|
||||||
|
data_dir = "/opt/spire/data/server"
|
||||||
|
log_level = "DEBUG"
|
||||||
|
log_file = "/opt/spire/server.log"
|
||||||
|
default_svid_ttl = "1h"
|
||||||
|
ca_subject = {
|
||||||
|
country = ["US"],
|
||||||
|
organization = ["SPIFFE"],
|
||||||
|
common_name = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "stockmarket.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "spire-server-stock"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
DataStore "sql" {
|
||||||
|
plugin_data {
|
||||||
|
database_type = "sqlite3"
|
||||||
|
connection_string = "/opt/spire/data/server/datastore.sqlite3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeAttestor "x509pop" {
|
||||||
|
plugin_data {
|
||||||
|
ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeResolver "noop" {
|
||||||
|
plugin_data {}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyManager "memory" {
|
||||||
|
plugin_data = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the stock market's SPIRE Server configuration you can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-compose exec spire-server-stock cat conf/server/server.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
bind_address = "0.0.0.0"
|
||||||
|
bind_port = "8081"
|
||||||
|
registration_uds_path = "/tmp/spire-registration.sock"
|
||||||
|
trust_domain = "stockmarket.example"
|
||||||
|
data_dir = "/opt/spire/data/server"
|
||||||
|
log_level = "DEBUG"
|
||||||
|
log_file = "/opt/spire/server.log"
|
||||||
|
default_svid_ttl = "1h"
|
||||||
|
ca_subject = {
|
||||||
|
country = ["US"],
|
||||||
|
organization = ["SPIFFE"],
|
||||||
|
common_name = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "broker.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "spire-server-broker"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
DataStore "sql" {
|
||||||
|
plugin_data {
|
||||||
|
database_type = "sqlite3"
|
||||||
|
connection_string = "/opt/spire/data/server/datastore.sqlite3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeAttestor "x509pop" {
|
||||||
|
plugin_data {
|
||||||
|
ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeResolver "noop" {
|
||||||
|
plugin_data {}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyManager "memory" {
|
||||||
|
plugin_data = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See the Registration Entries
|
||||||
|
|
||||||
|
To see the broker's SPIRE Server registration entries you can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-compose exec spire-server-broker bin/spire-server entry show
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Found 1 entry
|
||||||
|
Entry ID : 2d799235-ddca-4088-ba6f-bf54d2af918f
|
||||||
|
SPIFFE ID : spiffe://broker.example/webapp
|
||||||
|
Parent ID : spiffe://broker.example/spire/agent/x509pop/4f9238aaa7a93cf96ca3d6060abe27bc51a267e7
|
||||||
|
Revision : 0
|
||||||
|
TTL : 3600
|
||||||
|
Selector : unix:user:root
|
||||||
|
FederatesWith : spiffe://stockmarket.example
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the stock martket's SPIRE Server registration entries you can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-compose exec spire-server-stock bin/spire-server entry show
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Found 1 entry
|
||||||
|
Entry ID : e42e8d6b-0a0a-4e38-b544-08510c35cbbe
|
||||||
|
SPIFFE ID : spiffe://stockmarket.example/quotes-service
|
||||||
|
Parent ID : spiffe://stockmarket.example/spire/agent/x509pop/50686366996ece3ca8e528765af685fe81f81435
|
||||||
|
Revision : 0
|
||||||
|
TTL : 3600
|
||||||
|
Selector : unix:user:root
|
||||||
|
FederatesWith : spiffe://broker.example
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
(cd src/broker-webapp && GOOS=linux go build -v -o $DIR/docker/broker-webapp/broker-webapp)
|
||||||
|
(cd src/stock-quotes-service && GOOS=linux go build -v -o $DIR/docker/stock-quotes-service/stock-quotes-service)
|
||||||
|
|
||||||
|
docker-compose -f docker-compose.yml build
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
|
||||||
|
spire-server-stock:
|
||||||
|
build: ./docker/spire-server-stockmarket.example
|
||||||
|
hostname: spire-server-stock
|
||||||
|
tty: true
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- ./docker/spire-server-stockmarket.example/conf:/opt/spire/conf/server
|
||||||
|
|
||||||
|
spire-server-broker:
|
||||||
|
build: ./docker/spire-server-broker.example
|
||||||
|
hostname: spire-server-broker
|
||||||
|
tty: true
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- ./docker/spire-server-broker.example/conf:/opt/spire/conf/server
|
||||||
|
|
||||||
|
stock-quotes-service:
|
||||||
|
build: ./docker/stock-quotes-service
|
||||||
|
hostname: stock-quotes-service
|
||||||
|
tty: true
|
||||||
|
privileged: true
|
||||||
|
links:
|
||||||
|
- spire-server-stock
|
||||||
|
|
||||||
|
broker-webapp:
|
||||||
|
build: ./docker/broker-webapp
|
||||||
|
hostname: broker-webapp
|
||||||
|
tty: true
|
||||||
|
privileged: true
|
||||||
|
links:
|
||||||
|
- spire-server-broker
|
||||||
|
- stock-quotes-service
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
FROM gcr.io/spiffe-io/spire-agent:unstable
|
||||||
|
|
||||||
|
COPY conf/agent.conf /opt/spire/conf/agent/agent.conf
|
||||||
|
COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem
|
||||||
|
COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem
|
||||||
|
COPY broker-webapp /usr/local/bin/broker-webapp
|
||||||
|
|
||||||
|
WORKDIR /opt/spire
|
||||||
|
ENTRYPOINT []
|
||||||
|
|
||||||
|
CMD broker-webapp
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
agent {
|
||||||
|
data_dir = "/opt/spire/data/agent"
|
||||||
|
log_level = "DEBUG"
|
||||||
|
log_file = "/opt/spire/agent.log"
|
||||||
|
server_address = "spire-server-broker"
|
||||||
|
server_port = "8081"
|
||||||
|
socket_path ="/tmp/agent.sock"
|
||||||
|
trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
|
||||||
|
trust_domain = "broker.example"
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
NodeAttestor "x509pop" {
|
||||||
|
plugin_data {
|
||||||
|
private_key_path = "/opt/spire/conf/agent/agent.key.pem"
|
||||||
|
certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyManager "disk" {
|
||||||
|
plugin_data {
|
||||||
|
directory = "/opt/spire/data/agent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WorkloadAttestor "unix" {
|
||||||
|
plugin_data {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBcTCB/KADAgECAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWJyb2tl
|
||||||
|
ciBDQTAiGA8wMDAxMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAXMRUwEwYD
|
||||||
|
VQQDEwx3ZWJhcHAgYWdlbnQwfDANBgkqhkiG9w0BAQEFAANrADBoAmEA2t2N2LyK
|
||||||
|
jb5A32zI5ChQmnnCSacjERlIedTkW2URnriW/IirLUHJSmSNoybqq0ubbEbV2LTX
|
||||||
|
IKU7dbvabF9TI3M9N3J+eL2yvkabqoDj6srN5QZavuTWb/gMB7J6AskrAgMBAAGj
|
||||||
|
EjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAANhAHXsRyWbt+adfKty
|
||||||
|
a2fPwwJyBiq836cF2PI0F6KIztXSNBxAkRE+Ky4vE21b52N56KpuopbIf/ibaapW
|
||||||
|
7k/o5PdjUp3iAXIJXsP7d0qJ5By83/swsOkDY9g2/XYpnr2YBQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIB5gIBADANBgkqhkiG9w0BAQEFAASCAdAwggHMAgEAAmEA2t2N2LyKjb5A32zI
|
||||||
|
5ChQmnnCSacjERlIedTkW2URnriW/IirLUHJSmSNoybqq0ubbEbV2LTXIKU7dbva
|
||||||
|
bF9TI3M9N3J+eL2yvkabqoDj6srN5QZavuTWb/gMB7J6AskrAgMBAAECYHXF9wiE
|
||||||
|
HIK9uCcCYO/1ibo2fwgnOkV/N3LnzqIntt2UMxtdZ8+IsQqpJVaAIJNgsRjL/pTK
|
||||||
|
akSbwXXQ8RrmjXEifHVl+XqNibqgLgIuFLJR3C2fixWzzIihlbYcxw5WQQIxAPvO
|
||||||
|
vlvTocP0PfT92FrYkIVwVNQH9SKXkQf9I5GhII5BFA5fBguOENj5avAwziwBOwIx
|
||||||
|
AN6CZ3pCW1WXfugyO/9SsgK5kIoTN6rp3U0Lg+XbOiHZ68jzUGxS6IR/X90U5GTY
|
||||||
|
0QIxALyBy5Qm3MU7hV5w4pUv5xFeRMLuqh8ZZGOcqBIPk7WrFn6juHzR/97O6bWi
|
||||||
|
c9YRnQIxAIgsbn+YFKVxLa2U8Lr1NRQN1LNrx2nF7jW0kmgdnpoQ8AfvQIzKwJo2
|
||||||
|
CckXfB9rwQIxAK8yIMZZdGcwb6rHVZTkleorHwT6JZCV01oEBy6/KWHiJI11QdMt
|
||||||
|
4vLzQVDYWlYXwQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
FROM gcr.io/spiffe-io/spire-server:unstable
|
||||||
|
|
||||||
|
# Override spire configurations
|
||||||
|
COPY conf/server.conf /opt/spire/conf/server/server.conf
|
||||||
|
COPY conf/agent-cacert.pem /opt/spire/conf/server/agent-cacert.pem
|
||||||
|
|
||||||
|
WORKDIR /opt/spire
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBbzCB+qADAgECAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMTCWJyb2tl
|
||||||
|
ciBDQTAiGA8wMDAxMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAUMRIwEAYD
|
||||||
|
VQQDEwlicm9rZXIgQ0EwfDANBgkqhkiG9w0BAQEFAANrADBoAmEArbgurz3YC9Me
|
||||||
|
VlNPuk3yXPvSUfWelptozX3EZzCqL5yLuokSKgMHrZEjwcjYz5+OvwndjkpbZ6hp
|
||||||
|
atBOqv9ETjji0G1RnIWapq2iLYBQ45whPfyRFQYXCqad33CShiE3AgMBAAGjEzAR
|
||||||
|
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADYQAVX3ZQm6sY/ZxIf3HL
|
||||||
|
O81sPL1rMdxie/r+JUHgmg2hRAFDsagsP7GEd7MRVX8N7bltLsI0vAs33cMF++RS
|
||||||
|
1oadKVRaK6+UU1ouelfF4ESXOPxREeQc4kCRLueqoVwraxU=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
server {
|
||||||
|
bind_address = "0.0.0.0"
|
||||||
|
bind_port = "8081"
|
||||||
|
registration_uds_path = "/tmp/spire-registration.sock"
|
||||||
|
trust_domain = "broker.example"
|
||||||
|
data_dir = "/opt/spire/data/server"
|
||||||
|
log_level = "DEBUG"
|
||||||
|
log_file = "/opt/spire/server.log"
|
||||||
|
default_svid_ttl = "1h"
|
||||||
|
ca_subject = {
|
||||||
|
country = ["US"],
|
||||||
|
organization = ["SPIFFE"],
|
||||||
|
common_name = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "stockmarket.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "spire-server-stock"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
DataStore "sql" {
|
||||||
|
plugin_data {
|
||||||
|
database_type = "sqlite3"
|
||||||
|
connection_string = "/opt/spire/data/server/datastore.sqlite3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeAttestor "x509pop" {
|
||||||
|
plugin_data {
|
||||||
|
ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeResolver "noop" {
|
||||||
|
plugin_data {}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyManager "memory" {
|
||||||
|
plugin_data = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
FROM gcr.io/spiffe-io/spire-server:unstable
|
||||||
|
|
||||||
|
# Override spire configurations
|
||||||
|
COPY conf/server.conf /opt/spire/conf/server/server.conf
|
||||||
|
COPY conf/agent-cacert.pem /opt/spire/conf/server/agent-cacert.pem
|
||||||
|
|
||||||
|
WORKDIR /opt/spire
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBfDCCAQagAwIBAgIBATANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9zdG9j
|
||||||
|
ayBtYXJrZXQgQ0EwIhgPMDAwMTAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVow
|
||||||
|
GjEYMBYGA1UEAxMPc3RvY2sgbWFya2V0IENBMHwwDQYJKoZIhvcNAQEBBQADawAw
|
||||||
|
aAJhAOOiuSYdyfFhep+OJBkMy5RMbOa5aMEICur7euGWfclyco9enF5DEfd/wAs/
|
||||||
|
TGmx5a/cYfIbI/+LKGk51l6gKxlI7W7PLwm++chC9XDbKXNvGUW6Ljr0qPFgORHW
|
||||||
|
sWfTuwIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA2EA
|
||||||
|
BGIufwHpf45XAFlhtycAPTQJnwWYXfGAJ236q1/YinyY/PrcW3qXx+98mEBQ1G88
|
||||||
|
rD3gMy9vgUIooimHvpWbs3XkRXjW6GOcWgNgccYsT2PivOP3Tg2dqwKxrM+Mj+AZ
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
server {
|
||||||
|
bind_address = "0.0.0.0"
|
||||||
|
bind_port = "8081"
|
||||||
|
registration_uds_path = "/tmp/spire-registration.sock"
|
||||||
|
trust_domain = "stockmarket.example"
|
||||||
|
data_dir = "/opt/spire/data/server"
|
||||||
|
log_level = "DEBUG"
|
||||||
|
log_file = "/opt/spire/server.log"
|
||||||
|
default_svid_ttl = "1h"
|
||||||
|
ca_subject = {
|
||||||
|
country = ["US"],
|
||||||
|
organization = ["SPIFFE"],
|
||||||
|
common_name = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
federation {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
federates_with "broker.example" {
|
||||||
|
bundle_endpoint {
|
||||||
|
address = "spire-server-broker"
|
||||||
|
port = 8443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
DataStore "sql" {
|
||||||
|
plugin_data {
|
||||||
|
database_type = "sqlite3"
|
||||||
|
connection_string = "/opt/spire/data/server/datastore.sqlite3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeAttestor "x509pop" {
|
||||||
|
plugin_data {
|
||||||
|
ca_bundle_path = "/opt/spire/conf/server/agent-cacert.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeResolver "noop" {
|
||||||
|
plugin_data {}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyManager "memory" {
|
||||||
|
plugin_data = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM gcr.io/spiffe-io/spire-agent:unstable as spire
|
||||||
|
|
||||||
|
COPY conf/agent.conf /opt/spire/conf/agent/agent.conf
|
||||||
|
COPY conf/agent.key.pem /opt/spire/conf/agent/agent.key.pem
|
||||||
|
COPY conf/agent.crt.pem /opt/spire/conf/agent/agent.crt.pem
|
||||||
|
COPY stock-quotes-service /usr/local/bin/stock-quotes-service
|
||||||
|
|
||||||
|
WORKDIR /opt/spire
|
||||||
|
ENTRYPOINT []
|
||||||
|
CMD stock-quotes-service
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
agent {
|
||||||
|
data_dir = "/opt/spire/data/agent"
|
||||||
|
log_level = "DEBUG"
|
||||||
|
log_file = "/opt/spire/agent.log"
|
||||||
|
server_address = "spire-server-stock"
|
||||||
|
server_port = "8081"
|
||||||
|
socket_path ="/tmp/agent.sock"
|
||||||
|
trust_bundle_path = "/opt/spire/conf/agent/bootstrap.crt"
|
||||||
|
trust_domain = "stockmarket.example"
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
NodeAttestor "x509pop" {
|
||||||
|
plugin_data {
|
||||||
|
private_key_path = "/opt/spire/conf/agent/agent.key.pem"
|
||||||
|
certificate_path = "/opt/spire/conf/agent/agent.crt.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyManager "disk" {
|
||||||
|
plugin_data {
|
||||||
|
directory = "/opt/spire/data/agent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WorkloadAttestor "unix" {
|
||||||
|
plugin_data {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBgDCCAQqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9zdG9j
|
||||||
|
ayBtYXJrZXQgQ0EwIhgPMDAwMTAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVow
|
||||||
|
HzEdMBsGA1UEAxMUcXVvdGVzLXNlcnZpY2UgYWdlbnQwfDANBgkqhkiG9w0BAQEF
|
||||||
|
AANrADBoAmEAtmHF8/92WExZWZpM0qMf/K07jZIGJYhksDFBsfP96vybCoLaqJqa
|
||||||
|
S2kF/ZHNci1/JZmQj88+PBSsw1cgU9t+hGRiO4Q1gMETAoTTVKjwrZFPH9qxu2+A
|
||||||
|
ziiBxLHNr3YNAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
|
||||||
|
AANhALtDWHNK2KbJlBC8MTzP/CtswmpIySVM9fKq2FQIgy7Sljrz5gGEUFSL1TFz
|
||||||
|
bn3XN50dovVCSauXd8w5PO3QQ6J/SLa0z8gHH+wJvlWvAF4FTMTJkGqK/ZKDIwbK
|
||||||
|
pZ4H6g==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIB5AIBADANBgkqhkiG9w0BAQEFAASCAc4wggHKAgEAAmEAtmHF8/92WExZWZpM
|
||||||
|
0qMf/K07jZIGJYhksDFBsfP96vybCoLaqJqaS2kF/ZHNci1/JZmQj88+PBSsw1cg
|
||||||
|
U9t+hGRiO4Q1gMETAoTTVKjwrZFPH9qxu2+AziiBxLHNr3YNAgMBAAECYCb62Ksg
|
||||||
|
o3OVxdb/woGWecSwZbUJS6UD9LkvneHhyxhJKv3hH8i/WlDZvn0Gh4lqrZCnk6a5
|
||||||
|
lC75lVsW/xMHHEra45UUcaHA/COw5acuykh+62YAkAYrhXhw5SJBa0McZQIxAM0q
|
||||||
|
MeItgVnk3eHd3/yhMhuayJw6S1jgV7kard8PL0UjevBFLfBI+0tUA3BzydSHiwIx
|
||||||
|
AOOSb975bjYGkfPfKJSrkiKVd6DcK0o1UUne8hUcRz34BR0bbUNv2o16hpGtkNlr
|
||||||
|
xwIwCVEBMuQeG5bo/Hi20yH+xIIi2fVLtp15Xk531sk5vEoAKyj5DRBDWQhXn6Oi
|
||||||
|
ZqRBAjEA24bDjCCphExKNyqqhuALFHmC8RXyXJ+aTtxWQq8Iumqq5C009bzM43Wy
|
||||||
|
oo0AEfy5AjAhRc0I+wZDnHqJUE+JQzDpeuKhrAf0gY1zjDhFh6Bax+oGqZVCXttP
|
||||||
|
S0UyEhdses0=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
module broker-webapp
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require github.com/spiffe/go-spiffe/v2 v2.0.0-alpha.4
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/spiffe/go-spiffe/v2 v2.0.0-alpha.4 h1:S/TtS3UiP69IvrWjtjSF/qv+GiIkP2jkYfV9Yl712hs=
|
||||||
|
github.com/spiffe/go-spiffe/v2 v2.0.0-alpha.4/go.mod h1:Z6jOEo3L49OpNaK5JTIOig6K9HJhwH6cb78MF5mothQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
|
||||||
|
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"broker-webapp/quotes"
|
||||||
|
|
||||||
|
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||||
|
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
|
||||||
|
"github.com/spiffe/go-spiffe/v2/workloadapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
port = 8080
|
||||||
|
quotesURL = "https://stock-quotes-service:8090/quotes"
|
||||||
|
socketPath = "unix:///tmp/agent.sock"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
latestQuotes = []*quotes.Quote(nil)
|
||||||
|
latestUpdate = time.Now()
|
||||||
|
// Stock quotes provider SPIFFE ID
|
||||||
|
quotesProviderSpiffeID = spiffeid.Must("stockmarket.example", "quotes-service")
|
||||||
|
x509Src *workloadapi.X509Source
|
||||||
|
bundleSrc *workloadapi.BundleSource
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Print("Webapp waiting for an X.509 SVID...")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
x509Src, err = workloadapi.NewX509Source(ctx,
|
||||||
|
workloadapi.WithClientOptions(
|
||||||
|
workloadapi.WithAddr(socketPath),
|
||||||
|
//workloadapi.WithLogger(logger.Std),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Webapp waiting for a trust bundle...")
|
||||||
|
|
||||||
|
bundleSrc, err = workloadapi.NewBundleSource(ctx,
|
||||||
|
workloadapi.WithClientOptions(
|
||||||
|
workloadapi.WithAddr(socketPath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", port),
|
||||||
|
}
|
||||||
|
http.HandleFunc("/quotes", quotesHandler)
|
||||||
|
|
||||||
|
log.Printf("Webapp listening on port %d...", port)
|
||||||
|
|
||||||
|
err = server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quotesHandler(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Method != http.MethodGet {
|
||||||
|
resp.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := getQuotesData()
|
||||||
|
|
||||||
|
if data != nil {
|
||||||
|
latestQuotes = data
|
||||||
|
latestUpdate = time.Now()
|
||||||
|
} else {
|
||||||
|
data = latestQuotes
|
||||||
|
}
|
||||||
|
|
||||||
|
quotes.Page.Execute(resp, map[string]interface{}{
|
||||||
|
"Data": data,
|
||||||
|
"Err": err,
|
||||||
|
"LastUpdated": latestUpdate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQuotesData() ([]*quotes.Quote, error) {
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: tlsconfig.MTLSClientConfig(x509Src, bundleSrc, tlsconfig.AuthorizeID(quotesProviderSpiffeID)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(quotesURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting quotes: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
log.Printf("Quotes unavailables: %s", resp.Status)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading response body: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []*quotes.Quote{}
|
||||||
|
err = json.Unmarshal(jsonData, &data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error unmarshaling json quotes: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package quotes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const markup = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
margin: 0 20%;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.quotes {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.quotes, .quotes td, .quotes th {
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{if .Err}}
|
||||||
|
<div class="error">Quotes service unavailable</div>
|
||||||
|
{{end}}
|
||||||
|
<table class="quotes">
|
||||||
|
<caption class="right">Last Updated: {{.LastUpdated.Format "Jan 2 15:04:05"}}</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Symbol</th>
|
||||||
|
<th scope="col">Price</th>
|
||||||
|
<th scope="col">Open</th>
|
||||||
|
<th scope="col">Low</th>
|
||||||
|
<th scope="col">High</th>
|
||||||
|
<th scope="col">Close</th>
|
||||||
|
<th scope="col">Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Data}}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{.Symbol}}</th>
|
||||||
|
{{if .Time}}
|
||||||
|
<td class="right">{{.Price | printf "%.2f"}}</td>
|
||||||
|
<td class="right">{{.Open | printf "%.2f"}}</td>
|
||||||
|
<td class="right">{{.Low | printf "%.2f"}}</td>
|
||||||
|
<td class="right">{{.High | printf "%.2f"}}</td>
|
||||||
|
<td class="right">{{.Close | printf "%.2f"}}</td>
|
||||||
|
<td class="center">{{.Time.Format "15:04:05"}}</td>
|
||||||
|
{{else}}
|
||||||
|
<td class="right">-</td>
|
||||||
|
<td class="right">-</td>
|
||||||
|
<td class="right">-</td>
|
||||||
|
<td class="right">-</td>
|
||||||
|
<td class="right">-</td>
|
||||||
|
<td class="center">-</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<script>
|
||||||
|
function refresh() {
|
||||||
|
window.location.reload(true);
|
||||||
|
}
|
||||||
|
setTimeout(refresh, 1000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Page is the quotes page template already parsed.
|
||||||
|
var Page *template.Template
|
||||||
|
|
||||||
|
// Quote represent a quote for a specific symbol in a specific time.
|
||||||
|
type Quote struct {
|
||||||
|
Symbol string
|
||||||
|
Price float64
|
||||||
|
Open float64
|
||||||
|
Low float64
|
||||||
|
High float64
|
||||||
|
Close float64
|
||||||
|
Time *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
Page, err = template.New("quotes").Parse(markup)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
module stock-quotes-service
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require github.com/spiffe/go-spiffe/v2 v2.0.0-alpha.4
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/spiffe/go-spiffe/v2 v2.0.0-alpha.4 h1:S/TtS3UiP69IvrWjtjSF/qv+GiIkP2jkYfV9Yl712hs=
|
||||||
|
github.com/spiffe/go-spiffe/v2 v2.0.0-alpha.4/go.mod h1:Z6jOEo3L49OpNaK5JTIOig6K9HJhwH6cb78MF5mothQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
|
||||||
|
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||||
|
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
|
||||||
|
"github.com/spiffe/go-spiffe/v2/workloadapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
port = 8090
|
||||||
|
socketPath = "unix:///tmp/agent.sock"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
quotes = []*Quote{
|
||||||
|
{Symbol: "AAAA"},
|
||||||
|
{Symbol: "BBBB"},
|
||||||
|
{Symbol: "CCCC"},
|
||||||
|
{Symbol: "DDDD"},
|
||||||
|
{Symbol: "EEEE"},
|
||||||
|
{Symbol: "FFFF"},
|
||||||
|
{Symbol: "GGGG"},
|
||||||
|
{Symbol: "HHHH"},
|
||||||
|
{Symbol: "IIII"},
|
||||||
|
{Symbol: "JJJJ"},
|
||||||
|
{Symbol: "KKKK"},
|
||||||
|
}
|
||||||
|
quotesMtx = sync.RWMutex{}
|
||||||
|
brokerSpiffeID = spiffeid.Must("broker.example", "webapp")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Print("Service waiting for an X.509 SVID...")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
x509Src, err := workloadapi.NewX509Source(ctx,
|
||||||
|
workloadapi.WithClientOptions(
|
||||||
|
workloadapi.WithAddr(socketPath),
|
||||||
|
//workloadapi.WithLogger(logger.Std),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Service waiting for a trust bundle...")
|
||||||
|
|
||||||
|
bundleSrc, err := workloadapi.NewBundleSource(ctx,
|
||||||
|
workloadapi.WithClientOptions(
|
||||||
|
workloadapi.WithAddr(socketPath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", port),
|
||||||
|
TLSConfig: tlsconfig.MTLSServerConfig(x509Src, bundleSrc, tlsconfig.AuthorizeID(brokerSpiffeID)),
|
||||||
|
}
|
||||||
|
http.HandleFunc("/quotes", quotesHandler)
|
||||||
|
|
||||||
|
log.Printf("Stock quotes service listening on port %d...", port)
|
||||||
|
|
||||||
|
err = server.ListenAndServeTLS("", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quote represent a quote for a specific symbol in a specific time.
|
||||||
|
type Quote struct {
|
||||||
|
Symbol string
|
||||||
|
Price float64
|
||||||
|
Open float64
|
||||||
|
Low float64
|
||||||
|
High float64
|
||||||
|
Close float64
|
||||||
|
Time *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func quotesHandler(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
randomizeQuotes()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(resp)
|
||||||
|
quotesMtx.RLock()
|
||||||
|
err := encoder.Encode(quotes)
|
||||||
|
quotesMtx.RUnlock()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error encoding data: %v", err)
|
||||||
|
resp.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomizeQuotes() {
|
||||||
|
quotesMtx.Lock()
|
||||||
|
for _, quote := range quotes {
|
||||||
|
if rand.Int()%4 == 0 {
|
||||||
|
priceDelta := rand.NormFloat64() * 1.5
|
||||||
|
now := time.Now()
|
||||||
|
if quote.Time == nil {
|
||||||
|
quote.Open = priceDelta + 10 + 100*rand.Float64()
|
||||||
|
quote.Low = quote.Open
|
||||||
|
quote.High = quote.Open
|
||||||
|
quote.Close = quote.Open - rand.NormFloat64()*1.5
|
||||||
|
quote.Price = quote.Open
|
||||||
|
} else {
|
||||||
|
quote.Price += priceDelta
|
||||||
|
}
|
||||||
|
quote.Time = &now
|
||||||
|
quote.Low = math.Min(quote.Price, quote.Low)
|
||||||
|
quote.High = math.Max(quote.Price, quote.High)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quotesMtx.Unlock()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue