From 094cdb445e2c1aad70ec6c390c47e1f254771e22 Mon Sep 17 00:00:00 2001 From: Maximiliano Churichi Date: Tue, 25 Oct 2022 15:44:28 -0300 Subject: [PATCH] Adds simple demo step Signed-off-by: Maximiliano Churichi --- Dockerfile | 32 ++++++ Makefile | 30 ++--- cmd/harvester/cli/run.go | 1 + cmd/server/cli/run.go | 2 + demo/.gitignore | 3 + demo/0-setup.sh | 31 +++++ demo/1-run-spire-servers.sh | 8 ++ demo/2-run-spire-agents.sh | 20 ++++ demo/3-run-galadriel-server.sh | 5 + demo/4-create-members.sh | 6 + demo/5-run-harvesters.sh | 11 ++ demo/6-create-entries.sh | 21 ++++ demo/7-run-greeter.sh | 17 +++ demo/README.md | 32 ++++++ demo/diagram.png | Bin 0 -> 41280 bytes demo/galadriel-server/server.conf | 5 + demo/greeter/cmd/greeter-client/main.go | 79 +++++++++++++ demo/greeter/cmd/greeter-server/main.go | 75 +++++++++++++ demo/greeter/go.mod | 21 ++++ demo/greeter/go.sum | 125 +++++++++++++++++++++ demo/one.org/harvester/harvester.conf | 5 + demo/one.org/spire/conf/agent/agent.conf | 25 +++++ demo/one.org/spire/conf/server/server.conf | 32 ++++++ demo/two.org/harvester/harvester.conf | 5 + demo/two.org/spire/conf/agent/agent.conf | 25 +++++ demo/two.org/spire/conf/server/server.conf | 32 ++++++ demo/util.sh | 31 +++++ demo/x-cleanup.sh | 6 + 28 files changed, 670 insertions(+), 15 deletions(-) create mode 100644 Dockerfile create mode 100644 demo/.gitignore create mode 100755 demo/0-setup.sh create mode 100755 demo/1-run-spire-servers.sh create mode 100755 demo/2-run-spire-agents.sh create mode 100755 demo/3-run-galadriel-server.sh create mode 100755 demo/4-create-members.sh create mode 100755 demo/5-run-harvesters.sh create mode 100755 demo/6-create-entries.sh create mode 100755 demo/7-run-greeter.sh create mode 100644 demo/README.md create mode 100644 demo/diagram.png create mode 100644 demo/galadriel-server/server.conf create mode 100644 demo/greeter/cmd/greeter-client/main.go create mode 100644 demo/greeter/cmd/greeter-server/main.go create mode 100644 demo/greeter/go.mod create mode 100644 demo/greeter/go.sum create mode 100644 demo/one.org/harvester/harvester.conf create mode 100644 demo/one.org/spire/conf/agent/agent.conf create mode 100644 demo/one.org/spire/conf/server/server.conf create mode 100644 demo/two.org/harvester/harvester.conf create mode 100644 demo/two.org/spire/conf/agent/agent.conf create mode 100644 demo/two.org/spire/conf/server/server.conf create mode 100644 demo/util.sh create mode 100755 demo/x-cleanup.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..52cd20a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# golang:1.19.2-alpine3.16 +ARG builderimage=golang@sha256:46752c2ee3bd8388608e41362964c84f7a6dffe99d86faeddc82d917740c5968 +# alpine:3.16.2 +ARG baseimage=alpine@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870 + +# Build stage +FROM ${builderimage} as builder +RUN apk add build-base ncurses curl +ADD go.mod /galadriel/go.mod +WORKDIR /galadriel +RUN go mod download +ADD . /galadriel/ +RUN make build + +# Base image +FROM ${baseimage} AS base +RUN apk --no-cache add dumb-init +RUN mkdir -p /opt/galadriel/bin + +# Galadriel Server +FROM base AS galadriel-server +COPY --from=builder /galadriel/bin/galadriel-server /opt/galadriel/bin/galadriel-server +WORKDIR /opt/galadriel +ENTRYPOINT ["/usr/bin/dumb-init", "/opt/galadriel/bin/galadriel-server"] +CMD ["run"] + +# Galadriel Harvester +FROM base AS galadriel-harvester +COPY --from=builder /galadriel/bin/galadriel-harvester /opt/galadriel/bin/galadriel-harvester +WORKDIR /opt/galadriel +ENTRYPOINT ["/usr/bin/dumb-init", "/opt/galadriel/bin/galadriel-harvester"] +CMD ["run"] diff --git a/Makefile b/Makefile index a52cfee1..9e701a55 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ space := $(null) .PHONY: build -## Compile Go binaries for the Galadriel. +## Compiles all Galadriel binaries. build: bin/galadriel-harvester bin/galadriel-server # This is the master template for compiling Go binaries @@ -121,33 +121,33 @@ CONTAINER_EXEC := $(foreach exec,$(CONTAINER_OPTIONS),\ server-run: build ./bin/galadriel-server run -api-doc-build: - $(CONTAINER_EXEC) build -f doc/api/Dockerfile -t galadriel-api-doc:latest . - -## Build the API documentation for the Galadriel. -api-doc: api-doc-build - $(CONTAINER_EXEC) run --rm \ - --name galadriel-api-doc \ - -p 8000:8000 \ - --mount type=bind,source=${DIR}/spec/api,target=/app/api,readonly \ - galadriel-api-doc:latest - ## Runs the go unit tests. test: test-unit test-unit: go test -cover ./... -## Run unit tests with race detection. +## Runs unit tests with race detection. race-test: go test -cover -race ./... -## Generate the test coverage for the code with the Go tool. +## Generates the test coverage for the code with the Go tool. coverage: $(E)mkdir -p out/coverage go test -v -coverprofile ./out/coverage/coverage.out ./... && \ go tool cover -html=./out/coverage/coverage.out -o ./out/coverage/index.html +## Builds docker image for Galadriel Server. +docker-build-server: + docker build . --target galadriel-server --tag galadriel-server:latest + +## Builds docker image for Galadriel Harvester. +docker-build-harvester: + docker build . --target galadriel-harvester --tag galadriel-harvester:latest + +## Builds all docker images. +docker-build: docker-build-server docker-build-harvester + #------------------------------------------------------------------------ # Document file #------------------------------------------------------------------------ @@ -161,7 +161,7 @@ AUTHOR=HPE GREEN := $(shell tput -Txterm setaf 2) RESET := $(shell tput -Txterm sgr0) -TARGET_MAX_CHAR_NUM=20 +TARGET_MAX_CHAR_NUM=30 ## Shows help. help: diff --git a/cmd/harvester/cli/run.go b/cmd/harvester/cli/run.go index 0ab7b193..f5fd70ab 100644 --- a/cmd/harvester/cli/run.go +++ b/cmd/harvester/cli/run.go @@ -78,6 +78,7 @@ func LoadConfig(cmd *cobra.Command) (*harvester.Config, error) { } logrus.SetLevel(logLevel) + logrus.SetOutput(os.Stdout) hc.AccessToken = token diff --git a/cmd/server/cli/run.go b/cmd/server/cli/run.go index 91bcab03..d69f00e3 100644 --- a/cmd/server/cli/run.go +++ b/cmd/server/cli/run.go @@ -72,7 +72,9 @@ func LoadConfig(cmd *cobra.Command) (*server.Config, error) { if err != nil { return nil, err } + logrus.SetLevel(logLevel) + logrus.SetOutput(os.Stdout) return sc, nil } diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 00000000..f58aaaf7 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,3 @@ +**/spire/.data +**/spire/conf/agent/root_ca.crt +bin/ diff --git a/demo/0-setup.sh b/demo/0-setup.sh new file mode 100755 index 00000000..7a601cec --- /dev/null +++ b/demo/0-setup.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +SPIRE_VERSION=${SPIRE_VERSION:=v1.4.4} + +cwd=$(pwd) +temp_clone=/tmp/spire-${SPIRE_VERSION} +mkdir -p ./bin + +cleanup() { + rm -rf ${temp_clone} + exit +} +trap cleanup EXIT + +# Build SPIRE +git clone https://github.com/spiffe/spire.git --single-branch --branch ${SPIRE_VERSION} -c advice.detachedHead=false ${temp_clone} +(cd ${temp_clone}; \ + make build; \ + cp bin/spire-server bin/spire-agent ${cwd}/bin/) + +# Build Galadriel +(cd ../; \ + make build; \ + cp ./bin/galadriel-server ./bin/galadriel-harvester ${cwd}/bin/) + +# Build greeter demo +(cd greeter; \ + CGO_ENABLED=0 go build -o ../bin/greeter-server ./cmd/greeter-server/main.go; + CGO_ENABLED=0 go build -o ../bin/greeter-client ./cmd/greeter-client/main.go) + diff --git a/demo/1-run-spire-servers.sh b/demo/1-run-spire-servers.sh new file mode 100755 index 00000000..ce1bfa4b --- /dev/null +++ b/demo/1-run-spire-servers.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e +source util.sh + +one ./bin/spire-server run -config one.org/spire/conf/server/server.conf +two ./bin/spire-server run -config two.org/spire/conf/server/server.conf + +wait diff --git a/demo/2-run-spire-agents.sh b/demo/2-run-spire-agents.sh new file mode 100755 index 00000000..611e97b0 --- /dev/null +++ b/demo/2-run-spire-agents.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e +source util.sh + +spire_one_socket=/tmp/one.org/spire-server/private/api.sock +spire_two_socket=/tmp/two.org/spire-server/private/api.sock + +# Get current CA from SPIRE Server +./bin/spire-server bundle show -socketPath ${spire_one_socket} > ./one.org/spire/conf/agent/root_ca.crt +./bin/spire-server bundle show -socketPath ${spire_two_socket} > ./two.org/spire/conf/agent/root_ca.crt + +# Get join tokens +token_one=$(./bin/spire-server token generate -socketPath ${spire_one_socket} -spiffeID spiffe://one.org/my-agent | grep Token | cut -c 8-100) +token_two=$(./bin/spire-server token generate -socketPath ${spire_two_socket} -spiffeID spiffe://two.org/my-agent | grep Token | cut -c 8-100) + +# Start agents +one ./bin/spire-agent run -config ./one.org/spire/conf/agent/agent.conf -joinToken ${token_one} +two ./bin/spire-agent run -config ./two.org/spire/conf/agent/agent.conf -joinToken ${token_two} + +wait diff --git a/demo/3-run-galadriel-server.sh b/demo/3-run-galadriel-server.sh new file mode 100755 index 00000000..c01ace31 --- /dev/null +++ b/demo/3-run-galadriel-server.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +# Run Galadriel Server +./bin/galadriel-server run --config ./galadriel-server/server.conf diff --git a/demo/4-create-members.sh b/demo/4-create-members.sh new file mode 100755 index 00000000..33c0ad53 --- /dev/null +++ b/demo/4-create-members.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +./bin/galadriel-server create member --trustDomain one.org +./bin/galadriel-server create member --trustDomain two.org +./bin/galadriel-server create relationship -a one.org -b two.org diff --git a/demo/5-run-harvesters.sh b/demo/5-run-harvesters.sh new file mode 100755 index 00000000..995b8022 --- /dev/null +++ b/demo/5-run-harvesters.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +source util.sh + +token_one=$(./bin/galadriel-server generate token -t one.org | cut -c 15-100) +token_two=$(./bin/galadriel-server generate token -t two.org | cut -c 15-100) + +one ./bin/galadriel-harvester run --config ./one.org/harvester/harvester.conf --token ${token_one} +two ./bin/galadriel-harvester run --config ./two.org/harvester/harvester.conf --token ${token_two} + +wait diff --git a/demo/6-create-entries.sh b/demo/6-create-entries.sh new file mode 100755 index 00000000..a4835259 --- /dev/null +++ b/demo/6-create-entries.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +spire_one_socket=/tmp/one.org/spire-server/private/api.sock +spire_two_socket=/tmp/two.org/spire-server/private/api.sock + +# Create entry for greeter-server in one.org +./bin/spire-server entry create \ + -socketPath ${spire_one_socket} \ + -spiffeID spiffe://one.org/greeter-server \ + -parentID spiffe://one.org/my-agent \ + -selector unix:uid:$(id -u) \ + -federatesWith two.org + +# Create entry for greeter-client in two.org +./bin/spire-server entry create \ + -socketPath ${spire_two_socket} \ + -spiffeID spiffe://two.org/greeter-client \ + -parentID spiffe://two.org/my-agent \ + -selector unix:uid:$(id -u) \ + -federatesWith one.org diff --git a/demo/7-run-greeter.sh b/demo/7-run-greeter.sh new file mode 100755 index 00000000..1f692239 --- /dev/null +++ b/demo/7-run-greeter.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e +source util.sh + +agent_one_socket=/tmp/one.org/spire-agent/private/api.sock +agent_two_socket=/tmp/two.org/spire-agent/private/api.sock + +cleanup() { + pkill greeter-server greeter-client +} +trap cleanup EXIT + +# Run greeter server and client +server ./bin/greeter-server --workloadapi unix://${agent_one_socket}; sleep 5 +client ./bin/greeter-client --workloadapi unix://${agent_two_socket} + +wait diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 00000000..e9a14551 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,32 @@ +# Demo + +The following demo script shows how to setup two simple SPIRE deployments and have them federated using Galadriel. + +![Demo diagram](diagram.png "Demo diagram") + +## Requirements + +This demo only requires a `git` client and the `make` command properly installed in the system. + +Tested on macOS Mojave and Ventura, and Debian Bullseye. + +## Steps + +Run the following scripts in order: + +- `0-setup.sh`: This will make sure you have all SPIRE, Galadriel and the sample application binaries. +- `1-run-spire-servers.sh`: Runs two SPIRE Server instances, `one.org` and `two.org`. +- `2-run-spire-agents.sh`: Runs and attests two SPIRE Agent instances using join tokens. +- `3-run-galadriel-server.sh`: Runs an instance of the Galadriel Server. +- `4-create-members.sh`: Creates the member entities for both Trust Domains in Galadriel, and setups a federation relationship between them. +- `5-run-harvester.sh`: Runs two Galadriel Harvesters connected to the Galadriel Server and to each SPIRE Server. +- `6-create-entries.sh`: Creates the federated entries needed to attest the sample `greeter` application server and client. +- `7-run-greeter.sh`: Runs the greeter server and client. + +The `greeter` server fetches its SVID from the SPIRE Agent serving `one.org`, and uses this credential to listen for incoming connections. + +The `greeter` client fetches its SVID from the SPIRE Agent serving `two.org`, and uses this credential to connect to the server. + +## Cleanup + +Run `x-cleanup.sh` to remove from the system all the SPIRE data created by this demo. \ No newline at end of file diff --git a/demo/diagram.png b/demo/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..72666158f3ecf8ce1b29a9a4a1d4ab4e8ea6265f GIT binary patch literal 41280 zcmdS>by!y27X}Q{Ac&wK2q+**my*&TDBayET@T$Y27+`+cXxM*(%lcKB3;rAvkwaI zZ)UEUYvzyd`)1zjLU_+}&OSTVUVE+kzSr|fPDT_PlNb{L0RdZFOhf?z0VxFhEMcI5 zE97VeUl9-xnS;4Nc4q5D@M~*az23K1am=?iqRelboFJ3!h*gpBMFS zQz*NIQqYRu@n)QL)}!lLN^u_5`NM7Fnh@kz&1Y3%LyQCiM(y%nPYBvp-OR1idKb%k zZQ7Z&?raR)F;zMg8YeO7oRf}|fSKuXs*UmJE&mCfPkBgyk@V#Iv!9+WVdV*lH@Tf} za(oIS|A>|Nm|OhK*PXP1d*A8vo=_>|U7Vk5KI)2VwnLr33Sh9oP<8FU7;c}U?ytv+ z;;b>v5|}MhUV{hRNbl`cUvlE;;l!~cNYZ%HbC6=9;$PBubL>dg=$4*_QX%d>m}3v=!>%W(v8-)~w z#l^vIMLin>1DLI`rClHPh8<{X)I>?mPEA^hThG#h>9xM)8v`aM3oGam1YRd@@TY}= z-D`3u3v-w)w-X=5>zPK+?y zM}Hf+YDdJtR?o)7%Fe_RMh><6`i-T%9Ulb+^rGwk{`S+r$>hH`!EFBx3k;AM`h@u@ z(-Y?Fw!x#k&{b|Z6DI?6brBN_Fg@TM{G4oTyqEX?KYjAw8~^7?wf~;vVCVebC;#V@ z|HmhlZ4GRMEiJ$+?fCy&nST%e?}z^$fQ2g( zDcy?p-xZXQI`n_fgGM?D$bA`NKV$!Q1%tvF=fC#C83l3GIfzL9yMimSP5SrsP#o|E>t)cDVk}5X=$ZwwH8_|98cUJoMmdG~`}_ za3quvgRo-6f8zuF5PH0MJp%~dC8!t_T~vC=S95{@O#t$L!~H*g0$$aZRK7w+xx@6t zJuyf}NJ9wm+KHC}t#A)rdLgWG7131J1_DIp*HD_QcIQBZI1RE|BMg z?w<>Z`)tN?$_6;3yj6MYljUYcXgBZ4jdJL}MDYHKWWmC5SLeF|%HX3X`t7)^ufM+A zWqXzuN$S2kkRchx^e{rD?#tQ!WT6w7xlm6LvDM&!?NPy`HB8zVMH`Z_~R|ZrT*kSc=WU+-+^-FOXaH6qn*WA zR4f9eDtm4Y#UI}&r|j;VX?zmo18cgP-P1t9C>ONl7lP(%GVijx^mPa3I8MQ$X8; zO~xztRzRsyU&kGCU#ZcpH1_LGYNLRGeeIUH+NIw5H*iI$R-FckuR58;1P$!9A@%h$ zCE%GbdtMXltDPf92o3B>Dc;pHX~2$@QnTgSq;VH zraI>xx~b5fzkLwP1b1==@#Od78N*->l99~136&qB{=HpJ40wfL%q|7`C-y7D!xix# z9$h*Bxj^s~m&R}DJV8Z8#W#~LTW|$m`+q;>D&Wj_)M1qCps5M&|t%NaLiE;1fSnb>^CM;?3!3_AS2am=SH zKlzRp+?qjUa6M?++rWf6yfdP&&EX4J>({0V1Xok{e@!E1s1xO8mHViDxPnO(-g|Tt zegU|GCDoJm8PrR@ zWw}qnYAtAEt9YZOTBQ>Cr|Y&m>0d5PnwJJFj~A!H4WhKACMzr?nLd8r!&?I0?98Dx z0aq*wSnP8gh8~H^Cfhzfqh8(}DOQV#vbVU%=(pIY6WvOPxNOqkuY9mE7icTwya&6Z zS)~*C-Te%5ou;vv>s|o|VaR{_rzNzj3Jj@-GD_>sKHP|m&@Hm=wMr|O?ZTJ$ScEY+ z{D?7yxYFjf4*XAC+W~Eo}X-5dhEo`eu;OSsqQz8gjTm081v`#HZ?-d$u|L)k>{NaPc!LwE*{PXRb z3Z?p7%s|(J4eiPpfx{m@3x8U098VPc=6*E$XcuLXb`a{i4G;;OAI=qg3@0yveSH7m zp_gDhw>?w-PPXtQMFRo9&IRlz39s*E3@3LEaYkDIzTI(>P)>H--}3pAPE8FGI2d z(-t0`w3+wz#1_zlHL|@Hz+uK&qaVk&v3a~&$ccX6%4->3nmHhieta?*)$qHOw?M19 zB+b-)k!SX)>w01h5OFgrswQ>rgSU^bZ#1c z^S!;H;NUT%dvQFr5G;J_sVv`Kxbs{9XE(Fwk)&<+)3z_ELHrW|!oxQCstu=XW}^(O zZiiN?%-QojRfO@vNj(l<+k&;KF3#fCF|D1*ds_HDH1Ol7z{y%^ zvnlqAXW%!o?StI8Ac2{>{RuN6Qaf5I3w+fOt4bi@P3S8d@~`mRcK}7hs}^Ub-aFS0Lh|Dyj|wbV7laV6@S3q z(7jHujcwvz|W7thN&E!F23LlnXM;)cV$e`&ocFl?WxTo}B;(Xfm3ouII7k zuUl0zEE(si7le&8{a|kPbb&Jf+b=$wX*$t#8aBptHsi9I4_h;FAWeAWB`AR! zN=P_FfznAR_FOjY!Q7II+S+^<0m4dpnb0f$#@4+hQvmyZxA=4N8%QJ;Wg%v*o);&x1U>S7L+%IDV{fW6N|z_fb2VYI zQbNp8E*R|JWgJ`%rX5Rl3ET=u%V*E`D`EGOTwad&M8vxh?JV@z`djp?I24Y8?XPW> zXSDBeYCFqwvXWa!9Dvs&vy!ef7(X0AwfDueJGB)9@)59tc6g~#t%F%yN-N96kIB7X zIijkz4#&GYi5F7>7F*9e&)rhD!ujn%a6GacUWP+*k7$oKM^s+CzJS{!hIyzjI@{$a zZKmV7c$?YL_Wa9}9dXCn%F&_Koj<+^NXz)(q+hho9LdKB=PtIVkL9#0rm9fsyrty! z-mXPzpYm6%Wdpp>Ca9)agfn}o6`K;(G3H#9({0HngUPjketEl`X@CwvVQ~IUPe@j* znH`*qhh|SGjY^=E=pKG0oT8g$UEXq4(!^) zxAV&zmeW7^U3QHL25Ha+SHsQJv;2OIe6g#B1dvfFA}p7cBeqW$>i^y%ZyKv z^fY6;e6J}zeF9v_-I>!e*0-8#_U9OtjP@c!LD1)I3c}soyZWvm6igz0;wf727XR!t zvHof5i}l?u_M?uV%wg?8xOgLaez8aTV*%>)je=eohfZQgabDNxw^FE~E7(07cZW_t zH6D)?+Az5rS*mW>x(WwOipd5HXYb2=`bajy%(o(Y`=;lTls%}?@wa&Y2#)b&>#UGn9JX!T zI$qo39?cJk=j`s9$(SB+s(Oz1 z0$K!4z1Gjxt85uEd@(gYML5U9W7uw#BjHQi6ck73p8g`7Hd|*Ep#3rYcn;HUH=gr6a*`zR98|;D@C7}IF_TO@@Po~6W>|h? z*vn<-pLUuH*f64DR}Nuv-K^bgfMJpb@F~ZKvxz%Zi#NvXOmBLOS!6s!J!&*#(#7WL z$9{8JV)>*HWqf?@uFKsT3 zy+0}IJU5xq#&JK}BT&7j|1`nTI_TA}%dm5qL311P^d63^uoQJ0+# z(^$%S(SCZ!Q;FeW9$(kf;=w#obAu^^*|vuFE}XQL_= zZeH4>!8ck{my;+wZ1OrY-XX`>GzxOOFwdAD8hQwvaQ!fqgUq`zp<;Xqv9Ox#Rmz)l zu;o#QEXn?r*CP#V`; z^S*}(K!HC=^7HMOR(jIGyMc$ z!Us!)I7D+x`R(%Tri;JTZlgveDkeg|Mo z(TBoP$k&L>A24SvoarK0$vF8InC(vj-`H@kFo^%ZX=EEULn=D7(RU2YPzIQx=vVrp z(7=5Q1uxFewC7`EW9KF@Oalc)K<@O&hCB-mvI@a|lK;l01XalK&mhoT25p2tzfcE^P0 zuW=gy2rBaXi-pHncFTj59X+WQf17i5@J#4;|3P};0BA}&9_!0Ydi_=HFO z%Nqen8SF%r8|@31A8g%O8ZXwXqm#0@?F%K{A~-ikt_PXI02-Z76`sBtjJGd%_T_Gq z9OLy=bt0|4sJ0o#_I9~ZW-&SX`2o-9nR0F4KcH1bQ>u5r<~F&6A}%&5@3Tygx~!n4 z-Q}U4&Io#8xH?tX)6K?<1~@n#a_KAw)5Mi6bURyfRSP+67rJRF%{rXXu!)sybXX|7 z1Ydh|?2Clb*7>Dlq)EOyKRq&Rev6vVQ8SKS#Xk;ktLc>rjbxm!)wYWSJuMO_p>+MZ zs?4QF3$andyzbenLz%MT&Jo#`vke*UC;OE1V^MX8$Y|l=QBuH=w7~RN;h5dds%_ey z@9G)OR_vOvOdpDnO5la_?sT&nhDmcgt#>_OGE1D-PvEpnFoD6CjKaj`EtLA@VNaAX zV_ZUXJ&p`^;y2yevQE30YV&?4?3|yS#NK+q9bQ5L7FG}w*rTFVun7gydz9gwiBcn3 z+aKQOHA#DZ^aW2A<7{P;^B5$*G&Ol6Yy0C*S=O#sEPN-_l~ThpxrKx=EDQpnmE>-I zT;HT7khHIv+V731#-;{xcTRzv;&vXTGN$IMsVZy3&T!fVYJsCqL{HzmBQZNXnZ4j& zo0cO5?nBTkp&kFeevGeRgBQ}L*DW8Sf3|##1x!cVp-u-lKw9oQPFWwpo#V+7_nxn? z>>qB;1$>a%T^eZ7?mY*|QFM~ZAU+C!A`tjO_T&u7Fhrz&WW>U3*lPf!Go6a`3B-7_ zP`YzFmv-caV$;F%oC9_7ITCOr%cd3=Yh2>Uowr~4CEAp=`-AXg_{46r{-8!j3^fSQ zECuSGXL~(7n{|4T4C-&}Ou~voZanKpjY*OmuMrn()7E=629h)W^FzH`h4bK(jXfLv_RxQ|^RY$~*AlPk>Bq6UdjRde|O`2HN4TM7XV&k9F$>@hu>AzVIhVkEgL-AJaeJ zo*}--iZ$h!vJ!R_mfp99OZxL`HQyu>qd-V9&&j(JW{blWZ4z4PZhJVB%ioBMAN0z)FCQmF&x-B4bv5ydsAZ1qX2*Ng2kMr-(jCWT?=x>dr1>U&b|7{)8v`MV=bQ(JWf^DI=MFM&_8N5`RIrrx!d+57v1JS-VFiLyz3c!|LacfW|- zpQ!QN_NF1}{TIsx+O@=}Y4A@7?3zA{d$`77#o44hZ^$}u?*R}z`pN6(kBLhGoJJC_ z)gty@W)MDKuCo+7yTmd(Jeuoa8GLxaVp(Q@M0JB8x+DnVEMY)KwrE!+m11n67Or7tv_jXzH z6eKjtMJDt6yg9*7kjvgi(Ag&Q}tr0oGo44F+w+VKi=K)r*~msV@oT4yRv7n`4)@m$?>4X^CTUx-g}`k{T>>%$&ZDk z(`Z37{8ljEXBao6gF%hH2!3NUVn4zIJC>S}fp+FlQ~3 zyH{IKP7$n6%(lX_s|&$W;DSD>ss`k8FE{eA64=KXbsY?DJu&PZ=J)dd?N;wl(3oTf zMJi#xtXarqA5tFu@C;OG6q31~sCJLjPjnA^l8HcDf)psHeFIDyWi+?flW%W9&Zf=U zPm1yE6CKOl6pDXaz8@EK;6qWV;+69whvsIB-(#5c%KaMq0{1IC4yV29G_ECh#)+)) z!-xk6BCsbpc`nlta5}UCm&0*>?^_8P*vQ3soDo+aSOK@vVx0z}f`5~w49zn46dS?S zTQEd{f4na$A#@e%Dgd%EI;TY%I1z9ul;MflXx?Mapo$k#;Kn!T?^gc#z3e8yfYVKf zum684c37VA5^o)HKQ-Y)!Vpn>)t@9Zkaw?Kq6~~O1&2A{nyWN@1e_y@s>EN481&%| z5L-kqwMfog`B?7>5PXleMkxGE5VB$gYaP~CDwrMKd4ZE+_YQ9)W&iN7l8Oh*rTGoZ zBBglqA^6#&26^1T@ z#-ZGJzhB<^?>zG3EzQ*P;A0GHgVr{FZ_PfOd-#tme=M0K8bn$k1R^g4c_nu#&AtO>X zQW&*tuXYoNQNSvTVOY)SJ-#KJLpMOBr%1^N@qCbr8<1vqfVDkvWQfV^mvE2GNs~P9 z(mgNvG~rYao30@eP)V25?9#4qJFhSsHVmwA8xJO>Gl@R<`_7z^5+NV7k({NHu6Q;R1^DE!jgP91^LO*Qt=0@6 zizuIxRyJS;K^Nc?({fnGtJ0h9s%pJvs{S1sNpD)0_A8-XIaeh*C#q0A4vqA%ArsX3D1>0uStoY1nBK}6-FGjF>$5LhceeP9Cz^U#1-FY$I zh=5r~YnwygJkP7QujL~S=cAXOY1BfNhcZdx&9KtdOAU!EbZK7voFblQ)_C7dT+EO! zq*1-tXa69!sIllKDX)`uEraVBh(~%rEU$TXq2=Ih0F$%E%|2T)iX8SzOS7J9jhGI< zORw^Uh}AH>UYn7#|D*eTS&G*eAF*4i%*F~{)^|iamae{S$ZSVxk6|18tReHOx##^Z z)AiG-iS42Z2)r(R)%-TpkG?WBx%~N@%Zyn*-%%Gh{LFw3Cakf_urQbFAVMKqqY+&) zTUcRxskF45`F%M|@4HZ1LkJb4@^J<~pw2>H3b_K5bpa{h=JDP7r>l+@|N3~!8g)Je_~ z;+GC=;EwTd1Ud+{>T$BLkwOT*fb%$QRdgOQQ#MStl}_OoLcc-WG3fN!m=*XhTzbTw zF)?U6nNrHh<92Arlylz?_Xqp#9ktxbyV;-XApqJXmqxma8e)Q!lK;<3dI>6_?kc7S zNT7rSgc>zoUyVS@KO1$A_#RotC8RGdrT<@K)eeMP-J?Iau6(AKpg8b&lutu$Tsb`7 z0>MWMTlqA;YqO1>`gd8QKRZKF{<|A^-J6~luC|jFmC;5C&aHWXL0#d#*v%AR*F7GL z>h{6mSF=|?LNyMgj&T2WTl9A!-SIJqnONsq16chD80lgP$2)~__=eX{_QrIXBN^2) z-TA;FZMe5G(p+*%4PrKXXNdAXRjvaPrf^Iv52lMITMwpL1`ACfC~9S|zP)p(f-mB* z)c-KFZE2%+lLPc69{&2{gYMvm!rhe(dyKY&;?&V#Ge(2-oiK?Hw03vwY(!O;9mHO2 z<-Yr~fYDXm_h)WUQ#tea<3siZ$|5yk)#-WbC9HkXtZnNjU>xl_Q!6{rc5HAzi2-zM z*$6KnRfD>AI_V3`VyHLh@kPk&`h^z46=goTDrN~Fu@n9{)gpSjxY#*$dUQ0bD4U<> zb3W{VD6~bx_|z~r4<>!>9Rr6X z+ZP=w46LTZS$-ARwvR|e43wfk-p{_XoS7J@T~@K@39`7Z@tN))d#Hq3!HP2`nehvw z(}2Hj-iBxoArFgO7B^>?vZ#KaIu{lYr^dOPmyf7$9l7oH%gZu9~?+%wBP_Y5zU34p0C~OBkyAd{Be?u z#^xjyb-hlVlXbAF?CJ4yi-SvoebGFXkOSf7z8>{!`_=WsFQ4z1F+a%kMn*?-E?Wbu zceAtD+obpD3Q#Y~=#+QNbcdHp85}nHGu33Rxp&Tt^#odDcMg_>$?|h$X{IJDUDT{R z_xsr7g@`=0WxC51vt!%UceCE-nSiH5L*C*M++n=x0#BS*;G~a~r!HrMI?GSsuF~?$ z#H2I>%LHr7&!xTGxfHyv*_3*l#+XQQs42iqg6I`3+yF51)&>jJe7|6&8Z*mfty`39{Zl9@nm{a>y>Gv zbhQQ6$a3;WQX2zWj97r2bscQAku9R|@1s4`%_slU0~0PPX~H8D*#Nm*flQ9-d7`JH z5qb#Fg(xjRnvhnVH&_zoEjL;AsqU-J0vCGHY{R!Dq@P?NaP&J2MuPfo|3gyTI#a{p zn@Sjvmo(eBKa}Ib>_7d~H8T*o<(>av?8|_)VhM7ww$4dDyBbwvBM>@7*B{LAK=@F# z+dj-8N_KKWXZ1&4e%VEMsVv-;l@AgCb)N!-h0W#*o*^&hlGi~fA=s&f#%=O9D7XYF zZ-Rfu4o=MrpP>TN%ygINJW}^gy;$?r#)bQ0o^X13?jp7WUvrl7V?d{~n`XTI9MTsM zD^@Kf6!H_Q7zyd z=pp&#(`2b@r1J5P@rSczj%|`2o9T+W5^C-hN4(DQtU zEK2sIOLl%^2tCB5Q6K&AXr5z*T}VImH~nNG4#oPD=uS)w6Z+mhMjx@>zCAtxdf#qY zac%)lZr{Aa)|lRSp2@!X)=X*6>0#xsXfQ@PJ!GoX+S1Z6Y^l%n=EMvomQ*{Fv*)AI zs(Q$zVHYIf?>n(M&S&ug5=;5swW570SX*N$Nt|pNv`Drlf>`Bkf7)Xgpe19h;df2P z7Mtz@L1hdiErREMPP7rVFW`WacLlK1D)j=%2>h$Uk-24B_0JC^pRpGLS|trKFMv7? z&g`?8U#>leZegXj_6JFi*|PRDhE-7ye?8soAoG32;uD%xNcZ-K_u^Bl!NiPmsaOrc@!Z0$=}cy^A)~$Jp|-}~x4m;vK(4GhvOfX?t}2~; z(Fn}V?AJ0P)Y_t-Po4QX^9%yEP`YF6>@5VTzcch*=G5K zG<^QJCaY34D%l-D-9gBiWSk^t>dv0vu}(XXmGLijz1w6Qk$rAL7q1YOaWLR2Tptn> z*%7#D6FXx+roAyxncUBOrc~3XusZ7N#!qhitpRSti~3Apk(9Hyuhq(<*IicWq;sZH z-r}~h1=$I?LevPz3fFP*+iA1pg7iJ;b|>Mhg^dke8M+$j;JTbh8$o4Xd@DY&Om^=XA}z zB(eCtzw;G~`S__gsOfT)@=gF+HsGJ@hug#fe;n9|{ zs-87ccz{2?!Z{YP)+4bIWqj(wcdVn&cOaB{aU_dP)}B~cGUc8#m6f@sv!689Ia?<*VxL`}V`~M4qBWeU{AS$7`sI`n-RdkbPLpgO@Ub2hPv-UAkp* zf^;*pZOQ^X^V>!@dPkL>0}oqU%B~!+(6c+gQeY(QopyFqV{*=sNLSCpb5dZe8|k_0 z$a_*-YEM|9o57tW6T{RtCLffe>#*IHQeJUVS2Hvf6~>nys#_>3-8Hw9MY=DNP#iVv zQ~jvCcedKXM*vMi-G{r9%~;j-tPnN9LOV= zmi{zai*IlCPzprCjAwI;7BV~$8M>fR+gshuYWfKtcj($Bd(3krC~-m?jyWQJ#P;5M z;-^pR+L=jA;+l!R8?K3^3~iY|LDwn%GdP~zqR}>DDo%Hq4q~-sP+<@iLM2~E!8#o$ z&ruyXjTT4h!Wwvc^2ap>iw_j{B570dRIq>HVdY7j!Sk3PZ^V)A2|uHJ zal|g0E}OogcvQwHlomBtOzcgA8q)9}F0meEak2c4Sz?4s!SB<$QK`NXpZ1P0>cxV* zmRydBk7s}Ac$OEB&6(qf1U%f}lcO%k>>c|$)gINoaq_XVm}{t?L`0V8ZNzDYtXMLOzIU-DIbx$Vg}+YYBzupz#!`o& zweFibtyaI7rN6mogCiJSn1e}SFN#Y>XW$q104ORB@$O#C&RPFSxi8Wx9IQcs3ycN^ zQvwAhBxb*?zhqN_G*!+M(jpvhhVuc!#1!HnZL>^h039eTsOk??HNob9?c<=kEy5v@ zaCS{%JOz2~=dI?{6^vm?LC%rjztGtKWWT`(-T*(RPP)s6h=`H`_=amLi3PR9Gqt)~ zx&sm}gfqw~BkEqlZJ`zbSrId!zePc!py>eMJZ!vI6JI_Il(x5r7FljHLe}SJ2J`GEaNY*P}YNv7m&5wwpqeksed%PgLWn!O-yVu<3);ieI7v z$05su>HdJ>6&cPUkZ_NQjqL#{c|r;GbOMFkM0~A!mvT@7C6hg(X4F7)>Zxu-+3^F# z)ZXI*Ay_Ng{;VC_*w4}J!fwe)k&nY8!)3)vJlp_OX1f4rh*9KS{&( zt4BfP6#7_#uTq(aj>x^3{vgygdvWE84IiEJI<@FHS&k^$Y!jDpUksk13SLV|!Pz7v^j@Qefw zZ_g#PEf(1H78NUW=$^(M5`bEb->0Ly0&y`!AVizH?QP6opc)Wk#X#WkxhFX#$&##P+}Qes6W!JSo5Hli-O{$#IDdY6~}uW4k3vA@yN zv$6|-v}3MgYs}w4w`lnf*&bYWk_hPmx1AL>WUMU2BJ^(_0&nHtB0_mREMp+5dAaH& zQp4fa60rL-+2-#;4l4uvyT+l1dCW&J+mJ1! z0~9}!?gjtV>0>NJ;7O;~vFcnmL?So8LbCL7`yoLRn^z#8o+;jZsdTT<3rf_0j%ZEi{ zIpo_ zatq*)@lQc@QIPMg1EooEiOwyiNh8fKjjq z@gHYlk?$$`q6Bw58!baZMEOPe1iWde+Sce`{u}_KPXS}i5~b}BT@6ZBv=Opjh3?qa z1)5BmO3La_1*##h8in^%f z7SBB}=}z|P`|Fdn%uuO}@EqOu1jUC%I&>Q1k8%m1QY>xDG^Q|seGf)(zWL^x)0K{J zIaMnt1+^R8oE!>zk0G!WlZ4O3&i;J~I}#;EKb|_$9d&-Ze#*7ljhe3MS3_W{JC1R1 zT$*bDAyh_N3%3R*;=;IIkiBpau`a!7upQW>X=FcfCl@z|VWy_P0@rZ0X)zH|G%%NX z-*}Fs&QItw4;=1560i|5XWHQ501iO!{`e46vfHkeEx6_**7N#adYa@70KNN(L{Le` z;JfhcGCBgK51XcW@rxw`>hdOtwkkI{Mpywyx$()clbXUQ%5Hj}?nHOt84$VoqZwn) z81^N^H>wYe0j7;DEiJ$jwMuG&gKd9 z4ls&wg>8b)RB`=1(Htqk*)n!-7uxJ8Ns09EY~J!iv;y5*E2H_E*~&Cri3=%11}p%c zJv~xav;kB#IUFf~S3vc?$gyw=7g98JzZp~+@z;r@347`0iC@bP!9~F=u+x)m6z+jL z`klPl%|_W8?hFId`5(0^xdjr?{fTRanJ^<|-!=Xn)td9R9T0Bm`$+>V)C{$Bz6Aa&C0 z{68^3FvJAsH$y6Vz=;9k#ork#iyuU>^!-`7KOXrq9EgSdJ*}aRcs>2UujwaDqAqb` zt@ztZ&{YLUhI?EIzUSg!q#$?%reHp{81>522D^BJ0Xpw2q?6)8`+!^m36EOlvx0pV z781r|wWJ*KlIm)%W;Ql#M3fTLV^Dc@7P@%Z8Z99-!s-V-qU6d*>ywqT%C07tDkVW{ zVEDiI^RC%#TpDnSt0TOBan%ci|1Sb24bZ8}Nk{NT0)0o7Q-fp`-;`g0Iw|XX?ma*> zVWPO~ttdCE{4{@#OBDDh9U5y0u3EXN^5YdDO0Zn~NPwnsc6U&E#r$6j+OL4v)7lbg z4+zS90ZIMx6hWTv>Sg49;9c^(bi+5iF)$&y(%bSBiOcQ>+{xnhjbky8NL}d#(O^;_ z65yEg=4Qq(r5NCN1*Jmt>}FXmq9IG`Q&yC7w%(99aON8)!<&%In-1Ixjc9f_M7 zjGym@5cHoI0O?Uk!J~*1DZxcUo_W!iK_=1>aj_aa)HWV6tF<)z>-$@3k4&}fWz)`r zqH4;Vw%!07=j03LtlQ8Y|1Dy+_M7*g^`8QK)Ve+tLPevW>U}r@f>Kx@?{+%@zea|R zur{eu1KN z4r)VCRt43`E_|X+u<`_N&V!C=4f9k~fIBt9jZ*_K3s!+@vw0qlmCN4F zpqE^$yD4%HH1z3S3#Fq`;!8(jU@o}}%j%=xNO{vOt1X|#jV829^hv+(4Twv`s%uum z=Rzb4Vo!OaJgcU6F_+GCwB`5lfy0-huY?W#DQCoshqbe3y?ay@YSEQH03BA>mAMrz z0jgmew{5}?6z1JlHD_%|vQ>z(=mhLqb4k+OPKU9cVSS^FeiSt+#}q zXj;~nn~W92uV&YSB4XR^rSI(_kEZvDjZ$XbtS|aL3Vd=jUp@|Eee!-eZ_{4sqq~a` zM0&y3yvln$%?f&WjCvWK7w6Fjv!0Fm&n;3L*Am1Uw{G#Q{TSr~kei2mMreYVg&W{b zt4qug2*T$R@pFCp6g&0O(TGo$SCdFp&W^@_%vp1x1xMhfLt%na{?NE0$$j*rvhG_% z7&?=$QJXo=Kh5rkN-}NvvjEcEu&4*3;fk9*{v5~I_TJcD{v<#Olqp)lq9-HUY*gj( z3GGhzogd_~9f+v7z(Qls0TH&)bi_q^F{>;IElX?0VEnN}=2PFg)q>i$P0zjCgFJRq zyFmxy)Jycm<$FhE6e=?5X)J-ZT!;wZ%$Q&Cr%ZJ#J~GmD32fOH>2YgYWgswKF!KcO zP0kCbyn)oHV^M6OuVw5?W*BI0&rMozVfvmk|{#f+r)aEElXQFZ)5qmpr`4a>9 zt_>$j!l&bj+vP>gRL9aJ#kffe8CQ&FG8f3K9Ul3+|W#z(F*`bVox4UqXEe^<03q zsoM!F6fSO4*z9E*YYIB!1>Xa(lN(XJl9LYZVyd9ymR(Hb#x#OFa(+;tdRG!^S zoj+jje5&K(lQ{qi;oXT<}7^Wi0QO zDHE^3FbV9rCxCNi{LN7f!IF&CnE^G48_#~8vE;!<=y&+HG5BUE)d)C&kpAOX52!&p`4rpH0;M&KbtHf=E38*B#} z#oMbC1uXb0#yqo5k)K)nePH71T}oKH2#7mNe**^K@Qty;U16OtY=|0xZ=xp*8$a5A1^JB@~~+C8suyfC>eZpwNBN(i(NF7}Wm!qL%Be zrmeM)OXLzTY9w=NxaBQTD=@@MWut9KOl=CEo(08vB>D$k+ig=QMB54BHe5Weeu;~T zp8WT0ohD2&=OgyvT{f$7MZtFn2v~~v{Cd-dt;^1B9Ev47iD#E4)2u@wg5NctHrVYL zMulc^X2}*|RZ^{5y@72ZBgGOY^J{NJbNZ{rkItOm?d<4%KYR;% zhV1u2z~uM5dB$O}QJ~EbPzd)xrT#01F$hzc5-x;i74!TPwXp#YNR~NjLAgf{SWb1= zZOX;MZ%u*PcJr&DHfrK{sgt@(*56Gg5}`r|J^_gM(#~odluatQ@dHGT!CKxDz0!LC zZ3kQ)>1L_RrVmiSTk|~lJaD6VCS>~8oh&Ak)iHzc-CT}igBTa8p4?}K-S;XbdFlhI zff1OMoJibU!)7mFsyRIcVwH-ls0FJs$5)kV4F#wUE2Q9yrk-0dRRJG1#xyyRyVN+rmJ@S>9zBY6<}7{l;3!u0 za=xYJFT}264hz7AmRdr8nZheLn z5Fa&lKFek!rBCu2e!*)1$u(j?{bf~GP>p%aXc-Vb>RC2C;SjWTM9FnrwM~>9qw$u& z-VJ*jqhkai5Zbj4`K#%kdoC+7`xX`O);%$TnCCS4o6FDFN5^&ctWJ{eyx%TjAVxY@ zla68Q7~am5O&yr{Ri3pwrBpZUl%GEgm@&7&9e{)^8PaAyiObDNR@UvU2s2ex_O7y= zN)|>>ZDh-oz>bND`J6%Xe=F~;qpEtN_d!5VLKILzK?FfU5KttfL%O>`5tQaiOGqgQ zQX(M@(%m6Qsh}v`4T^+xUOHwUjL&y|vu4(sS+nM!@joy3p7Wmfob$eWKl^!}Z7$ZO z7nRobV$g55ND-TfxP#1pQ#Kz_1cbxg)eLlzHCE-XS`IM)V4jcBEs?38)}R ztemcJ88Gz}ApW-D)#6RH@$5lTQiYX05JO#z&(HNQpXvh2 zZFC>4;`@K-Ark;}IrT>WQLXdfqRj->N-6$MU#2JkiT_szp}vKsnl6{+s{6kwT1#L^C?|}+86vg&*R(9HTd@sQnxJ>xdD61a;>4U+{ z<#XW(f@JrV?oHHLq5YdCVHIO8&ApIIwc9sS{dI@gHGp71WDG#bR)oNYnAhYR2)J(A zqg5#C(0M4Fonz3pNA)}S)!5E3qeYqG-FIh~;?nK9;XO4)aLRRbfyXF|E~LT=y=_8A zZWa5GO}1(0+a;AJa-$3i`NO_OAx1clfUCYVGM=T%P%#db%u~)XPgTCoGy%ElzV*8w zC+%RLjiwF0^AXASJ}g1ZpY`2NI+5bu&Ns~##D9eXk(yCcDU#f{Kb~|U4KOX^8?v8k z4Vi`#f@H`p3b>S(L{`La@O0fa? zZg-+L8hnku{c4{Xhk`yk7e>q1@u7e(R37|4w{pJh+b&Ec-0udtTIEwl#6QOSz^o22 zWNO}8C}}@AWwrwez^kwV;3J4Q^JnFG9@edU)RccV3_k>PuJVZX@KzxE^_0ST$ng^G z1lMNd1cun!ty6$1TE)L9YpdjUfJJEuO1gfWW~)Nd8y;XoF=|WU_vnn^_V@dhZO*(e z_MXnV-80Xgv=c-33mzP9{My=qoQl=N`EX|k(l^F3^GN~`;V%+L%k%MaX?)i}mzH0> zxWL`@U1)C9X`Is1()H3ajAr-urz3$nfyxAb6IR5&&qNRiWi22y(Z|T18f3_S*c^#7 z6-dF&%4hSsHb!gQo~} zj4`SPB#rFn=;a=J<%}PJ)-ZMzGjsFjGXw7?&ys0HTpA07%R^^2j{K?FIogtOt~agE z2n^HCwz=|-n^(SG47H!(c2#F!{9<5+*ES1t$Hu>HW7>5s_Xxvbpb(kV_>X@?LjW1LqT$!>!H>-5k~kRY|dEnLXH@2Ynj#HPWz zl0=pHDI`lSM@NDqD*gK8B9m1?eJqt+)tVg|0UAfrPc`9_19RdY-gOW7rDc5h~EZAWOj9#ypmE6VPnDPXA{;_ZLr9{WE#Ji zz8J*PLpehJCUboUNxQ2M=CZ(0g4EWXCoA{2Oli*;)!lzTbEvm$u)j>1q5_UX_O~oYN7ypQSz<_)wXPtc%%-&ued#ghiyupgb->aX3Xh^ zpP4|Gcy>L7>Y!=BPzG`O`g|%Bw0ic=_O%<*+^W+IM~(aAM~6;_*`x&rJ3N8y#21!^ zKkQWEILwhwI$lc4-|0lHz>S?{(*AY#16%i3V|`3x3l??7q}PilxtR(&5-BwYho(Q+ z9$9f7v5F;r7;E{NuzRpGl_#*f=0>60{KXatX7I6gY3?x&+JsmaVM+5Xd=aR)`#kVb zOyQfHAj%U}cwJ!FZwwxA@}Q2%hqR zyI*A<{Pr|88xS9(h2#-CKEL;k?4-!n9YnOEX>MOxrL-oJ>MgEcRc>@CD1U{~Oox+> z?{kO15=6aA%14#}Mt)F_6`fJ9TL`$f|DaIbLh<9_<<%JzK3d5DeOq^Hk=MU)utSN3 zs;d^>ZoClMzlz(ip(9|gsb}Mp5M%y1ZGggD?~WXex9efU!c&VSvH0emb6Jz^vo85O4iV??GwpbG*AyOYS0k2n zcGC;J&YffR6U95iYHS>dsco$>%RmXT{kVIX2QgP!jS10O-<@Ni;_P1mJFZ8-kb@Kr~+4n$U|$|WnO4KeDh)b7s1N)%LhirSKL-a zwwN&-t#5ASwQFkEIB<_WNg-f5VD~Uf8}RtP|2_k4f$fNo&)uXuo?cR?Y*87H>LN?D zn{hZ4JTF#`8Xwb-sYH-2s|B}^X(&70Z{H1 zu#D#W^2NN#j6R*~uNyf@l&F)}i(L+x`D^+!AAM!jxEpAnu|Xee`Jieb(Trs(z?O%G zmm5*B#K$^8J{{Ng%=TLrdd4rFs_Q^5xvllE*og;Ci`v18nUFNH;_OYJI?{2!9$_V- zX#CKca8TwaUJ09iPclhWw_=@>zI@kLRqloDSVtxn*}CRzR9Th%#EC~wnfte?)1X$G z+m$?s@Lh1#g#`k4Y61U#K=HiW;pjq`!iajrnp5hQT< z=qe%Sw<`f=9^zku{3V%?GSpY0&qY>mS>MiCH-a7T`}dt_mU}1K9a2K9-(3acNm+Nyk)0q=;acVjxw6qb?hrY3=>Up? zN&J}d6{4a^kyzwTIYKH`I8hAjqhAvH;9bJP*)I&i<_ZGKAK~ZN*=J}j@l(7_kyihg zfbim}QHu3tB`o}N#ucU(g|QOzUR1;Rn}hfviHu(Gd9+xP7uN37X%&9#I~uEZ6naR& zbr4twKN!1CVw>OS0COxk?W*qQ}5CN=Tw5R)FAX3Zf(Sg*g z?_)TUW%g(b*Ollkwg>lBep`Th^MDbEBA=c~L_n(N<$z}iRs8vH9}z!+Bqlgk_V&Or zD+ei%na0(B^dX=qU(bHl1av!JbtIJ(KEQSX!_T_-A_gRZ@_LPZENidTG0x`oyL|J$ zFw2}cSj7_E@^V}p63>-T%Xo$HB?}CE5zbr6XJJiBG5Wp24qjweGr&67MF(j5WC68I zjin>=RtI*dIu$SeziML_m>@^j8Yu+)%S*E%70jVeJua{#vq$|D=JF4hh~!2G*qLBg z1gFEz2*(CqA%z3CQaDBy0hSq&7N}OpW1{`IHh*=}5-%nZ!x*0;l}d?G!t+lx%>9Im zvB9I`b?(Vd^5YB3#(@LmjPc^$mc4aM2v+n#~Z9i1#@6z7k0Sh0*+JjTSWJ( zT~41NQ5U1W<^`fpA2pSo#$3%qdX|cd!1iqR;nV^-k9~>l)Hm{QJ?&6Lwg!Zge4uWc zX9_Qm)yS)P7-^wjepBoxyu_aR_KSbKCIW%Nzd=MH)w|eXO_{Mi0kuB)H0~fyPR?j> z!!0r|$Rs23X-fuPjbwb*{R0)7!xptDuwknYRR4MQ#Mp`J&SwM3IkP{O;KcUodamD2 zly?)%m=}25ctNvgkK^ZNzr+#g>9@3{kCBoW*j*B5D_7^J$cHSwv-v64pRcdFO2|JZ z>1?%_vpA?0(hPE*KGLS!@SLXBgLKB+_bipQf&jWlsxL%_PLY~a#CUDs=U_78eEm>EvE>{@6JbQB_%g_DGl_}vZ_{3uL>+gKWvH>ltIv2)j#5}v@ z%vQH=YX?TWvF%TT&n$L)xQ_TwF)Dut#FPsXn}TQ%2x%AI_uBg?1jd1OAMqI8jeR8v zxJrTb+fY8Tme7rDlCxjWsEudl@+oB1iAGTR^w4$Hv}LJ|2gqZt3C=TfzA=cgOP;NfXBe3pnwJYJg-yOqz`l0WaH=8*!T#y4@bOtY^2w`$(cSJ=+Zj)O^* z<&U9&{!^njEV>#`uZBaIeSCr-w{&_!n(c6qtWx5 zcIV}dg_x?mi}S(lQGv}XvMN02_$7@lv z#^<@m%G!TVOid1h?YwiQ2gq>0&*T>YSD1T7^CvT(X-Cp$O>gqw&z-06CzuS1W3l<4 z3n3DML2pyuHvfHlgo6=Z{5A^%YNfOA2N%YYv%imN&WQLqlSe9ag6}slh!;Mp{5?Mg z802Gzia*v;*YZSF`{@5Mc@T~PFu2>k)QtJhLrzWf8ipnH>in}mPXcZ#VvZ;;8C(7j z{JaVm=Zh_U>+dxG?++sU04Iu>z32s3F(IvA;KJLt4}O0yoV5eY6}oPRo%14slin4U z(1)%F-$BTwgof=D<_ zzXLI{fwpemwwY*%iO`q%0IN9@ov!&>ODuQ$4c+n<1bpqJGvEH@=4BTw*JJ~mI3eWPo7)CGGixFB!aS6EI&L#lR{O^M|F%-x^sXmv;y>2!p*$11HyipHrdbHAoMpO<#SrH1Uri^qIZaz z9H@od#^df8;Bmt(;Ii?U?bMmezsS}vp1_Pxw4ydca;e)uoUiJA0xm9(0Jd`&rs8{9 zA$-2g&W5N*f2&dO6os>U{vE&b)q?k5oS__=yTw?Z-y1|zEh=IG3(9i~Etg)TAh2z{ z(v>3%P&lKiuvaRpL+>8H8*E46+N#A6*y$kw&(EP>GV}TivBGH&i;zA^Gt4zF?o%$7>SPvD0K)EOLzvQc0a#8SpCpY86%gw_;@?E7p{h(phX8 z*1{*N6Mp@#=8o5c9@V5|>f+A+@AG&KwM4?-Z)1KIUsE_RG~g+s0g=-rZ+mp42wd2GWY;)TX%eP}3-1$_;EDP$prB?zAv zN*v|4z}|%ecv=Bwz&BHfhVny#GbCxx9wPfDh4s6)gYJ8at&n(<+~^MjB4iZY$H^Pr z0hEpdAGXi>1GXLwu`P}>qIZmDbNfMnxS-^Z+!yZ)+vhdT`{q0ZF|9^()7R}fvP8Ev z|7Zc%a@XE`eZFpx%8L$ak)HDsU9O2z6DaG|t97fi*n@ad#p2cY5IIBgF8Y zdKti3)tk!@gq@p2?%FzsQk3}B=16Rc0C{>HnO@HH#(dW~3($XQoTsBpDlUF{( zv<|9{H>LV1x8va;(cl~L5UH?8Xws?dZ5@p-l0{1O27D`J{T>p1z&`Afem?KVe)Z;k zJ9h(%(B~$wl-MR#xLArMGjvHv_ohQ>q~o&t_K*CFXnyb$8(08O`+Ui+lflP)GB^`A zTg}F6-Gmw;w9*yF0mEseNK^9$rjZkY`rfVk805$w-0KhI2^FH=UUh z%nqTWiKFsTWuzH@&kHJdQ_JtBmN(6hfg0Pyr<>X(jikDz0}=#Kj(X+YT-LGWSRQi~ z)d&Fn%>A#xcAOm&G;VrzUt2A4AZZ9;bLSe=454Wx%MyaJ$?DX48F#*IMI&Mrd#hNam0OD1v3uhw^F5}gF}&I)WO6^?CT z4He0LU>7KQlYbjA)?h|Kk)&OF>nGLG0E-Zy`?ituKFj{qP}s2TYmMW%ZFEAtViZa~ z;U)QHypzUE>pq0_Bee^|>~=`VID;^#qSH9&-!Ds-e>zRB>gBi8d*so7i>~o*J=N{D z&gj#ixLek4Yqwx|y_VZ45IUlz`jF@lo{SiT`xo(mje_h3AU%$^H%UZ0#{}!%e!dk9 zKA<&mTvjnV!1~zFetD%gVAl0ghFr9Y@}((DW44<23!AU2E5CaajI4v_V>i;2Adl4U z01X|6fFEe8m-$&KyB7Ec<*sUq=I&LH9R#^>L8Z>(jRzQc++?EV1CtD0YgbqQA*4;i zH1K0?BlWqR-mpC|1i>EcC2&K$7!9OY_<`*)TAuv{Q4&ldN`eMiMEJW3QoH#NIdeGR z^qBV6`hL;R!$8*IG0?2)TQ&5vFPk*?c(~AFK3~SMK%L zf3+nysbYppmzjyph$SMB96|mnA}vzefGN+7gNp5Ky2>v1?T8j#GmV)3t`?ON>_Cpr z^B0I2rTk(U&97t8n?>&>E_ar+yYq@x2@d&ffE(IHm^_`VD*z(Y9=N??wd_NWaTF_IMa)&khK zGXxZNQLy%Yrsyi}+yax7dgD@`fN6Q31D1QW{5K62rKcPKbROsq_HRgC07s^>fm^SMNB?sWr>4-vP{>_|CYhs3Mk1P8S)Aj`56hK6Ih@tX_ra8j#FTh`{wmku&gO zqmiawZG7l-f`N5&wrH`c=c&V_;>nsHbv+h;)s*>sr@~gbRtQ1EdDUhol{T^;iL329 zhu2I>Ha5i4J6Lr#XnxQZrH>UtNx3K{+hrnH=w{?2g}Nw9mElJa~tT2MW4EtyvupI5=pUk7NK%pjDlsTC;R(3RYEHZ!)~b9 znXh*e)!xQydvV33=}g3Y9CnItCY@v1g|OR%X#ufvHDtS=s9$Mf4XqKl#$z1rIGov@ zdEh~t?6{dKk4YSjrHoEBYfG74Hmgn8eAy%DPlB53?W*Q)wF`f&yqr9KZNs}EtwJh; zH=~29wsRHfiL)`oO}Oe3V6&f5by?OlYu~UOwy+oabGA^&D5zM5<)&lKIjsh2U7DKy zu+@C+wV0C+r+o6=tJe;FMsFKI9T2jvtP%sR#TLhTT|BY6%BRfGxMWeN^%gd@k}%fx zOh3hiBfa{w6$LVL&*z?BrBO{~ElL%Nu2^f9qDDG#OvAfh@`bz>t)b%3to$jD9o=QN z`hwK?oqZLK^<&W*PjE}+h&lIUI@)BY18BmQqgtRrx2ec`F(-lhj`7Eh4>!6W*WPQY zddoSA$48viIxsr@D@JvwiG#b-r=L_xB%O-D24xHzvvvx#-U|$DdH`PHT+dL;l-QvXHqOKYTX?KCa;#x zpuOxDp&4b@2PdO7id=^&2c1(6YZ%aC2`uFUG>kInI1MYlZvLjBjNNw@X4MkJ$!3#x z$%#CLO>n;_zPI}#P=ngK`k)j7$H3^4e+0DtH zKNBX!ZGqj0pz+s}ODpZ4cQy85HeUL$V0;?9&Y9<`_I7&9EH)8w5LwdywRx$hS~8DG z;lWV<3K&NgGS^+WH~izW+scP&Sl&yq$_-+49xezper_x3`DWwKZ>9UwWB={6byXw1 zh}reJc*mjZzg`PC0a;{NlVQypYpydmriAedN&Jw4RL%e$Dd!uN*f2nklmj)KEpW39LC7RPE?T1w%#5IC*CT&|d^~RA|&|n!oAZ9oD(wBZ7goL+9zib@C`~ zyd9M;yL2G+nd?dxnx1|6Qtt%;^Nw@UI-Q-1?dKFOpcEc7>i$}LbZxn5ezDjPomnF> zjh6@Y&4OmOx@vL<2n@>&s|^F@c#gcbdLmykJhr;Hyfd`lLroj46BAyDnIHJSgZDv{i9f)p)5npHfPhW9=XQ>-k=7z^TZySkMTf zDAA})KbjDEGj?aj1vZt&cNDbFKAbza)4-0FGYgL06juu&+2qAs+}Z}N%s@uwufS>1 z-9*6?8K&49p*`IWJ$JJYxXVn8UzvFYSVXPviZPmLs>Rm#q6raS}j=5KQg8eMdYzSyNsQ;_rqkzO-s9Rh%ITO_%yGUL z*ziRKl6oPh*Y1LA*4YeQ0&T2R>S^Ibp2(jtafLCk%!s9~(Ttwcs?JmCVmdVopZxt} z1NBYN&Ru-leKtl)tm;d9+P%vv&;R|7$WJs$F;P(2yX=M>4e?-c*Q=X<4+)D9D=syS zU8mf65;&T$8x{yPf~ov3B1fhn5f+oc7Qcxb9`Cv3*H%p1qzVq9#&IG2`-~XI=kB=p}{JJt(j1pNI} zQj*@9$O&<1I%X?Xa73lmVGfC~{KbtkzWlg5_sQ&dG%L8kW4(9=Ihc+Nb&;Q?S(emg z802L{H*1_PcODnH@IgHLE^bPLn1$^`%nxHy7>BjJbnG=m3V8+wHLyBxEu5R z1y7@UCrb@mUB~FJtsua+h22XzAtOIB*K_HhINjw0^tcvt|*!Aeyx<%Ji z+SJI?G5k%9w!yuJ-Huawf&uL5tw1srCZiwT1}UYgbWX27KXifPB51W5wfRD+8?WEo zMa5+q61V*s#o19=VJYXMx4>`3i5Jb^eZOV4?I5F>XVX48tbW}~a) zj_nPkC0Wgtt&geFVJ!d-O-RtXox+`L6e5r(7fmCc;6Pa(IG1Hh29A>nP4!X9KBu2O zZEAmxRBi1JC2fvI-pi4}-s?wkT4KC+q}})RMpA1+!I&ZBbl+53{~f}K5VEZSHoc#p z)wl0Fs8HTTFBy~k*k>Aq6-7`F?%~J|adkl-j%PRhUsMmMSys__TUjieagr=-Y zx4F^pJroA`^P!i4XEzIW7P1NOm{LE(26G}IK6|%T&VJ)U`}0|T*fFG`;?GIcG9EP) z%d)jr3dn%gF`9L@yt)?r^QVC#b{h#%Dt^q}IKvIG}7g+6%x0E15wqFBj<$0rfczrRS}cq7_isUS$#4Up&0{ozAnTxXYp&C z-tl5Y9qzf_4yBJI!j$XWbJ525GK}o3sbUz!(IidD3@PvR2+x9vQ)?fO*}x=1GJtpy z)tRGkfc)4v#&h1M2RAgmx=F~%{H=Jpi1NE0JZc(MNu6qJw)YF{x1HA}S)Iy}M7DF2 zi&-O=(ZDB*7+E0Wh#uQrEsJWi!r?IQVdPu<1wyeTZgz&nsQw3BHHyXt%nFq$=k7q= zE=Xh1+;kHBByRzOAqHt_8F)Dyd%9DSic;W~s3@{@g7*pdcyYw`fy0tXu)wC)`>$?* z>9l}{SCAX`ux*-e%yTRd8(+fkQALrwlH#9^GHO9u*I8GuN*tGmpwVKl>9*J&5fD@Y zG~{Y8J)=Eda!gNQ_7lu&uM0#x$S`)VRi8+;!vm(S+ z{rf3a*GfanG%RFIAN*eN46yRaR4T97G!Snpd+O+=nD{jbS)8!iw{sm%wXD2K)7&?K zHSh7`ytolibqdFv^#ImoXam`DZJG+2?~>J}muS*ct4vbA-#@GX4E-1D&T|plskuB!(Son;N;2 zrYo2N`lp`>zlXoFYB>MSR2l$h(oZFbJlk%>qV-IL@_5*u!g5%C zO;Po@hrC`;we!UK^L;N8c;X}9t!DB5+YzN?;3*Ejy!zis{9&ZBBim|}|4woZhA1Z! z{N(SDg_9bvi9+eD-LlUd@6^cWuVMG3r<3IWeSVk*r3Wws5^h0tNI3o+TpErX_L}y} z-~SUa-hz4cr%24ci#(yo_qJ1Zv_e9L+_t}m85dgDT?eaI3 z$1pB$?wh@k>cjaa&Sk1GH5OdsY`F|$nTB@&0^C1iS%BGkR2Rf}_1}P-{%?Hg8Wj#X z$l?U?Wv>6-|4s4W9I}P48Gj!z=>S_RZPb1GVH+{A_H`xFYELue%3;Dv zMg_yq{2mcebUhTfXkO4!W5>WPv5cf%{d;66ULm(_;FsE^KcmOQ<&cGx=Z46{f0IiB zHz~5PGmHJ-1(`;|G--P`{CAV+uEFFQrl>yrcPBe;z^F^G{m=fp*)mcX#M&PvGQRzu zDEK@{8)o$pUgV8K5c|n$7-bXu`LZw({FvxMn-59UZIj>{uE*c&J8tUzc~69sw&8X+ zqJBp9JO&Opi}V|@zZcYW3VtUz;}|hpCvjldq6x$5zYyWtgNL`VG|Fcvmn=ek31)=dy21O;6aBItTIb$Yzk;vo7CIMXkAXp{AT1`M2B(^+ zu*v2G5{-udnr`J&X~@YC~#k#yJN z)Ueb_M?A}m?paZqk6L3MLI`&*%a*Nr#T(&O()th*aKIj3C|k*2XWB_qV&2PS z2X2cFrTdFS#Yk^pcs=FH5@f=97qe*SioA)VxU6(OnD^AqAu_~AP@$qPIE{3N2W9`T z;Qh%S4zDX0+G7;xPT|SL^Y(%TNYNBs@7z0}pmd(5v?+`ER1?|HNRja-P{H@%4XV zsbYhc;D`cyfl!2lz_SnWak`Y&Q5Z$EyLqq*q9}K`cv4)7wlR3lWd$aNm1U@~k!=gvs=9n;&?)G(auYaJvUHL>jj*jb$yxwqIUO>m(KcB> zU;jpHFCI)-MC65OuPHx8^*oj*dkN1H5|%KpwD};PPJTk0E;_&=jrqRwya#@j%xiIaqw2e5o-S1r7*BXVK2N}|Cs!E zxzkZh8f8Z5-lMhk6o%3xO>=i&&w2QZ@FQGP=9{OnwCU<+ZtFCMONg=aa1EXERzhC4 zArWEM`UgZ!Oqha+ojU0pG_SuFbqW$a|MP;uR;-K*zmV_}x%ltJ1)VFbSe@v*Xq zJB-f$4ih|u$6&=+AM*|ua>CxY;dmPBAwzwu+Mp0-PaQ6NU))rD4oLy2O|aiZf75=Z ztOuV8i=7nyasKb?IYn0pJ`!shf)cI7p||c6BcuPbLBYbQu*w`QN+?T5a|-+uu-IS! zT|Z%~VvO(`0fLN~&HotVdE9UrvGn?YYt*mEk=Qmpk`zyb8ioDjrPi2E;F@R>A1ozK z^l?ZWgyBVc?)&uzEGOmGh)hp-b@??x{jCg>LHjtUI{tz!5o3_wq~YAuwy^5IHEif^ z5(iDOz@4xE1ta0lU^BVAD97@55%(0%hO5t-xb|n7;Al!`V3n~gy`sT_)GcMzS|7( z*gtMGHVsA$rvtM&&LWa7SWR^$FI>zFVh90uBE%LwDks0{n|tMOl*5}`rihovhP+1& zg?*EH^a^pN?wciv(Et>dZn7^UlifhS0>!s4ZqTHb6Y&YSueTH54quXRw}o;6;py(S zuw!#1sE93~T3{g@j&`_j05aHnEr_+=I@G8f-?KU*Db5a9F)dFuLp5IvE0(zLzf!r#va z2wDo;4q!#{uz}_(?LjYeR3xjuc^mKi4c9k&jco3>ZpJ$%R3pW$rCM}j7v+PD1#qJ% z$5Pn|7+00@kgD=pA=t=FNj8_S&k?&ChFl(-O`E~jv44x9&7NPYT%r}slqVt87 zlYp6IMi(NXOIVtU?|7hBK%VD_)A-X?=oesRpYgK-)Hisvemyw>@$(epRI2LFAGIYPq zkC@i{*+q0IrRze`Xxp2E2C!|hQn>R2J3u7i?H z$TeB<&aw24Ztsn42_l{Cqn*Hfy*Ih9S@*60FmzlwE#sac;z*>U{TaHfEiovFzi5!Q3BcF^nyAFClQ>=*wzsIL` z@P5Yt4fd36Po`gO-CUWyBk0X8wf+oi9|s&OkIi{9R5CDqQV&?^y=~9#9n5JScO!I$ zc6$r^);sEWtp5Ev0TZ$uIzOjL=A3>2N>cIFG)2Y6;|oRzAiZ8`nHyNMexceSCImCxeOd)A!RxFXs_&Rt2yt+oOQzZ%f@H z1Ra|ZOd0Kdwoy5~vS3%haT9!Wh8(dgx2ns1VJCe)kMh{lb)ABuT$X$EUGJERnnkP4 zcKtVZUx#(^OCRaU$SXvaJOTM!D`;yBX(iKGJ$Pu*T(h!C?_T~SyGQPb*ty&f)6_bU zp8SOZwaI1xxYSm1wsCBHZ#hZ}c^!GKC1~zPSHV;CS>~o&?sKY%*@Pt9f@jDGi=;dQ z29equVoEBndPbs6yuTrp>*(I$+M7N@*#(}rc~mF;ZHrWPkjQXYvikyuT5_X^wSra6 zv7SbfG?YYzoLyyb>PGK6<0|6|*3Q)OUXx7i?!tG17VfGwku18?Lh1t6*9Km11e#xs zIkHo3&296#_k7m%wOU;N*W{@;*Tmvxr{z_R=AzG$;M0nX1qM;_adA{7T3J!E2-_z* z9C5~X%R7*^+UyPW*OD?(>sQism7_@q?{quq)`|s)Yz%HzbOIv*C7wF1WPVYPemu!} z20uq==Lcdt!^*?Vp0fQKyi7D2b6up+GYhb)o!+l$8+XRx=Rbi2goz>J%+^iYb z1HvMMRzBAix@>>i4nf~d;nU#}M+avg#w4ctFYp$);AmfFWvy*Up3a%GR&)9mLzSZb zd@f$iCFYYVGmV47wC|&hy9F!ec4)CV1^@IjtO`8k-23vLg@*!>tc{oAozg%Zs1}r{t z?~S!SJN=E@e5lGS%tIw4)nMu@hmSOO@>{F3ZDm6f~JzYFtH7i@@Ld9g~h=drBQTN2+;krPO=5aW^M*o*s`bQ5uw znVtw>E2mMZd$FtEoljLIjYd&4s$KVBu2{NAe5qbzc)MxCXl^;UpNCKtv1H<1crZs9 z9<>_lt3|i5pU(CX+IQSnoK?qCNgtCV^qGru=_rkV<(tbXc6Bu?L7IX3f`Rt}_!Skd zYi-b8CBFWpDSh!}C1>5fpD1Tgxofs-@%f|sq(yhnj1rKP(hcix<~Xt3P*`+6nt8ja zLppZKfBQyw^F3c`}M<#ky7^cDSqmTA^0WnK<}D7?dy2Zf%hh8?cCWh8og|> zt(WNG-*l(8!CyQr&uGSJ!+?|_uivQpP$>7x;mt#%9kFQlWu2mnx3f@hgfxaL+cSBo zuC36~lzs1rPK-So(4Q$QgJ=`eZPY-E1ZAsad$FyD;sVDl-5Oo%hp_}X7rvHF*B0{L zL;1uuym9Xt-4`Vc>2C|^ri^Mi@+oEzJ*1Oo&b)F}TTO9Z!N6|d+5|^WYJw^{=4(&) z+k-}qg@uNPdaSIpgw4J9Qu<*SYG*UM`e`n(OoYyA(c3J$h#lB1UTb+KmxzPj-gX`S zxOV0xmA!&0`L!`n7V^B=4ee_6+N;ib!;ZICn2?dgzTKWR^9e;>zBN9$S5P)Ffc7k- zSxip5#C3V}D6oDu2M+9NHFug`?~=SpIN~(Zfu}oTQOMC7@RbIPeUlM&S z=ajkt)?90Ea>Bdxt6Y$Z(Ij2xAeVpjef_N2{K@X+0=kwt!h?SH?pNgv5iKkuS%kDl z->vJrHt5XK)pZ{oxgIUYPfJ_qCUUg)-ZeY9qZz+-G(Gz?%8dAWBnu=jo|_#V#Z_H& zOr=&rKT*`!#uYeBL5i3)MI;IKK+j&4ZjFfu!6^>@9O+Lsayd;S)pV`R zHh1Y*3Gp=iFI!!`YPasQf%}1aCX*+Iq^fNMi)1lJ-NL3jJBSKhL+WqpCl>J3+^0Ay zt3tRYl)XZWk@ZdH!%n3lxg9f8T>^7+O$(1nTB@Q?*XO==cvH0!ti{Hq*ExOi$h+`m zF1p82$acV5#e;e6syyy_AJ5G=2ZAY%ejNg1Z)@C?1w)K{T@4;jWYL&-<$`xN3yWaV+X%6ZD(t8siQR<9kMnwJ>Z(ACkk zCiCqlC0P-?oObi9VIu`eu(~Gd0;-YXOt1Ngy_wNajp{4kURDsBH=Yx^fVv`yj^Q>| zksoecC8bkq-#J_D|GD?c7+J*xPu9zdM!Ck}Heh+_VrmCX;6=?jD|%}1hidOMFbFEI zUVbI8%+qc@0DD&jxdsp@#0noeVos#?W?o@!9nwUlESq0yXp2?VS=G3uQEfQMI#Yuq zqo4fw2hEtw@Vwc=Yl#lnCWbn6ttii@PRJJ*_$u?|1l2Gss#T9+xEd@@-cT504L<6P zy<*ti!+g@KnB?*J*LUtbfp45X>8x6B=ZqWeNbZ>J@b6&Ew1*csowz42@r9RxipV%Y zDfbBhPR^`LjpGGWZ5i5RiMxK7p_rUAm~GYi9ef@@nAfgs@J$ugeF=0X>CMUdVT#IT zRE&E_^(k((>QS1iI4421I@APxzI#|*zcb4HA=>`(c>a@@1@`8xu`%bL$S>yJF}I5t zd>@+mgh10G<%`SmeyNix6|=!0*1UDz`Si0|Bqn9<2fHsT7G>27uTyR@oT9;;K|fZn z7AI2=-c`8{RZ9=F-l7+GNEa{Hn@HCPtx&0rrKlX`>V%?~&jZc{vE~|}akditR0-#; z%nW^utS*vKE9fXgsEn?25iq@g;=8KL<-s#jBo?!!Rs8asiiL9JlaRNif#?&RO+8Dj zs5+yZpc$(Wl~s*ojZal~n3kQOdz$&m9cNA2oiiKHQc8(K1sN|#27B(g)2%k6?@K?a zj1_wG!WCUkmh+@rhsmHoofB_)wS03gGj{yu`n0NMHBYyGL%6IeR`0NLvTRRgtyt)! zuAa`4y8DQBPbm_yUfP@sdahob;f&MQJigcx{-8Fi+K{Gec=W9-7i1zI zua8m_&tj*$6^~crs9%U^U+tbpI**+t*@I|@Q2Q*-x+mFKsk-M(Zj4T9(pw9w8l)I_ z^X}+TYmA1W#g7-|A*;24j_THnZYD+fp3TeJa}?JsiULUwo+v;Q z7sjygh&x10%$PGXG3aJPbbrsKR=`LGP4ZB^*0>_>H*dHPII{?^J`YCs4$0gw)ukrm z&SvW@e8=$0l4Hj~pt!6rIbEHCZPwMM8x^!iC~c9lt+DG^7L<-@=3zpZ!fiAZrqbOs z^6|@~E+$(&4U;dX`gucr31((7q#vcUX4ziO2IQ(+2g&8&A21U<`zR^T%xK+X7Zb&r zky&?#^o?v>DTPnOO;qWsV+zMRsI1vXdN#yQKiaD(lui*F^HZ9Xd?JCSBMGv;Dz9}Z zCH(@a(9#Yt%fJ0%v$9A-g>-Y7_2=ZyMwlDQkElOdN!ebN= z#IW3!^-mKQ|AQAh4jZ+Mu+m6EgKUWYqOJ&5i4Pdzd70tzoog`*1N;Q|l63cD=zr8DhKmyKW57bx5py zSf6|eOJ-(TqR%~v4l943wQ zG0rN?kG0Y8<)^qQu5l<_TR@&@G&0NQ<1hqPaZvZOHSr2Xw#%?W{h5LOT1@S9d|WhUHe5ziOl9EXhX1uh8@+~bGCl*|iM z7jN~oL(gJUchJB|NWJK^ai0?>ouUssJ##$2)X>*7)Y+Wnn+`74M*aEZOIc$0*ocv@ zDAP5l5ZJ%0_l^JdZgeLY%=1P-{__a%fVbMm4QqAFZ)~{q5Fq};Aeagu=P!F~+XUE` zRqh6_dY_ih1{On>?g&4*4;7f0BW*`g?h_V-PzjCszLhM#%7k@3PPoqf0N>!#nnuAG z%1R>(RL0*{S{9z-xywR1#8e*pHb|EzK69bIP8-zTp~ zD-JZ2TngU+6p2zs1K>03)Bd=U52~SQE$-Eyrv{NDTK=a@W{8P zo*-R&>316Q50{IP+v`sirxaUMO?l*Xi>g9tbe16bS0m>5aEjERuUBb?abt7;yZg$x zg}m<>S)>L2`-lQ66aVX%sA&SaUiX~%^QHmx4bNP0P~lhnf1jN9flvN_eV~1zQFoH& U6pV2)4EQH4t{|2#df)qh14#v!-2eap literal 0 HcmV?d00001 diff --git a/demo/galadriel-server/server.conf b/demo/galadriel-server/server.conf new file mode 100644 index 00000000..7d5a88fa --- /dev/null +++ b/demo/galadriel-server/server.conf @@ -0,0 +1,5 @@ +server { + listen_address = "localhost" + listen_port = "8085" + socket_path = "/tmp/galadriel-server/api.sock" +} diff --git a/demo/greeter/cmd/greeter-client/main.go b/demo/greeter/cmd/greeter-client/main.go new file mode 100644 index 00000000..b2000dec --- /dev/null +++ b/demo/greeter/cmd/greeter-client/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "flag" + "log" + "os" + "time" + + "github.com/spiffe/go-spiffe/v2/spiffegrpc/grpccredentials" + "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" + "github.com/spiffe/go-spiffe/v2/workloadapi" + "google.golang.org/grpc" + "google.golang.org/grpc/examples/helloworld/helloworld" + "google.golang.org/grpc/peer" +) + +func main() { + var serverAddr string + var wlapiAddr string + + flag.StringVar(&serverAddr, "server", "localhost:8080", "host:port of the server") + flag.StringVar(&wlapiAddr, "workloadapi", "unix:///tmp/spire-agent/public/api.sock", "workload endpoint socket URI, starting with tcp:// or unix:// scheme") + flag.Parse() + + log.SetOutput(os.Stdout) + log.Println("Starting up...") + log.Println("Server Address:", serverAddr) + + ctx := context.Background() + + // Get credentials from the Workload API + log.Println("Calling Workload API...") + timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*5) + defer cancel() + + opts := workloadapi.WithClientOptions(workloadapi.WithAddr(wlapiAddr)) + source, err := workloadapi.NewX509Source(timeoutCtx, opts) + if err != nil { + log.Fatal(err) + } + defer source.Close() + creds := grpccredentials.MTLSClientCredentials(source, source, tlsconfig.AuthorizeAny()) + + // Establish gRPC connection to the server + client, err := grpc.DialContext(timeoutCtx, serverAddr, grpc.WithTransportCredentials(creds)) + if err != nil { + log.Fatal(err) + } + defer client.Close() + + // Make calls to the greeter server + greeterClient := helloworld.NewGreeterClient(client) + + const interval = time.Second * 10 + log.Printf("Issuing requests every %s...", interval) + for { + issueRequest(ctx, greeterClient) + time.Sleep(interval) + } +} + +func issueRequest(ctx context.Context, c helloworld.GreeterClient) { + p := new(peer.Peer) + resp, err := c.SayHello(ctx, &helloworld.HelloRequest{ + Name: "SPIFFE community", + }, grpc.Peer(p)) + if err != nil { + log.Printf("Failed to say hello: %v", err) + return + } + + serverID := "UNKNOWN-SERVER" + if peerID, ok := grpccredentials.PeerIDFromPeer(p); ok { + serverID = peerID.String() + } + + log.Printf("%s said %q", serverID, resp.Message) +} diff --git a/demo/greeter/cmd/greeter-server/main.go b/demo/greeter/cmd/greeter-server/main.go new file mode 100644 index 00000000..dd5862bc --- /dev/null +++ b/demo/greeter/cmd/greeter-server/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net" + "os" + "time" + + "github.com/spiffe/go-spiffe/v2/spiffegrpc/grpccredentials" + "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" + "github.com/spiffe/go-spiffe/v2/workloadapi" + "google.golang.org/grpc" + "google.golang.org/grpc/examples/helloworld/helloworld" +) + +func main() { + var bindAddr string + var wlapiAddr string + + flag.StringVar(&bindAddr, "bind", "localhost:8080", "host:port of the server") + flag.StringVar(&wlapiAddr, "workloadapi", "unix:///tmp/spire-agent/public/api.sock", "workload endpoint socket URI, starting with tcp:// or unix:// scheme") + flag.Parse() + + log.SetOutput(os.Stdout) + log.Println("Starting up...") + + ctx := context.Background() + + // Get credentials from the Workload API + log.Println("Calling Workload API...") + timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*5) + defer cancel() + + opts := workloadapi.WithClientOptions(workloadapi.WithAddr(wlapiAddr)) + source, err := workloadapi.NewX509Source(timeoutCtx, opts) + if err != nil { + log.Fatal(err) + } + defer source.Close() + creds := grpccredentials.MTLSServerCredentials(source, source, tlsconfig.AuthorizeAny()) + + // Listens for requests + listener, err := net.Listen("tcp", bindAddr) + if err != nil { + log.Fatal(err) + } + + server := grpc.NewServer(grpc.Creds(creds)) + helloworld.RegisterGreeterServer(server, greeter{}) + + log.Println("Serving on", listener.Addr()) + if err := server.Serve(listener); err != nil { + log.Fatal(err) + } + +} + +type greeter struct { + helloworld.UnimplementedGreeterServer +} + +func (greeter) SayHello(ctx context.Context, req *helloworld.HelloRequest) (*helloworld.HelloReply, error) { + clientID := "UNKNOWN-CLIENT" + if peerID, ok := grpccredentials.PeerIDFromContext(ctx); ok { + clientID = peerID.String() + } + + log.Printf("%s has requested that I say hello to %q...", clientID, req.Name) + return &helloworld.HelloReply{ + Message: fmt.Sprintf("On behalf of %s, hello %s!", clientID, req.Name), + }, nil +} diff --git a/demo/greeter/go.mod b/demo/greeter/go.mod new file mode 100644 index 00000000..62b93d4c --- /dev/null +++ b/demo/greeter/go.mod @@ -0,0 +1,21 @@ +module greeter + +go 1.17 + +require ( + github.com/spiffe/go-spiffe/v2 v2.0.0-beta.10 + google.golang.org/grpc v1.50.1 + google.golang.org/grpc/examples v0.0.0-20211001222728-09970207abb5 +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/zeebo/errs v1.2.2 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + golang.org/x/text v0.3.3 // indirect + google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/square/go-jose.v2 v2.4.1 // indirect +) diff --git a/demo/greeter/go.sum b/demo/greeter/go.sum new file mode 100644 index 00000000..ebd012dc --- /dev/null +++ b/demo/greeter/go.sum @@ -0,0 +1,125 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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-beta.10 h1:UXfGMp27MlQcYCAVRl21+cZrbKXMLsFmMXam5W3qBIA= +github.com/spiffe/go-spiffe/v2 v2.0.0-beta.10/go.mod h1:TEfgrEcyFhuSuvqohJt6IxENUNeHfndWCCV1EX7UaVk= +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/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +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-20190108225652-1e06a53dbb7e/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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +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-20181221193216-37e7f081c4d4/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/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +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/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= +google.golang.org/grpc/examples v0.0.0-20211001222728-09970207abb5 h1:k1HwCrvyzmToHY1nDSfCGU63gsShFOG46m7dks5rdRw= +google.golang.org/grpc/examples v0.0.0-20211001222728-09970207abb5/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +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= diff --git a/demo/one.org/harvester/harvester.conf b/demo/one.org/harvester/harvester.conf new file mode 100644 index 00000000..c6111e4c --- /dev/null +++ b/demo/one.org/harvester/harvester.conf @@ -0,0 +1,5 @@ +harvester { + spire_socket_path = "/tmp/one.org/spire-server/private/api.sock" + server_address = "localhost:8085" + bundle_updates_interval = "5s" +} diff --git a/demo/one.org/spire/conf/agent/agent.conf b/demo/one.org/spire/conf/agent/agent.conf new file mode 100644 index 00000000..f5b65f73 --- /dev/null +++ b/demo/one.org/spire/conf/agent/agent.conf @@ -0,0 +1,25 @@ +agent { + data_dir = "./one.org/spire/.data/agent" + log_level = "DEBUG" + server_address = "127.0.0.1" + server_port = "8081" + socket_path = "/tmp/one.org/spire-agent/private/api.sock" + trust_bundle_path = "./one.org/spire/conf/agent/root_ca.crt" + trust_domain = "one.org" +} + +plugins { + NodeAttestor "join_token" { + plugin_data { + } + } + KeyManager "disk" { + plugin_data { + directory = "./one.org/spire/.data/agent" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } +} diff --git a/demo/one.org/spire/conf/server/server.conf b/demo/one.org/spire/conf/server/server.conf new file mode 100644 index 00000000..2476de3b --- /dev/null +++ b/demo/one.org/spire/conf/server/server.conf @@ -0,0 +1,32 @@ +server { + bind_address = "127.0.0.1" + bind_port = "8081" + socket_path = "/tmp/one.org/spire-server/private/api.sock" + trust_domain = "one.org" + data_dir = "./one.org/spire/.data/server" + log_level = "DEBUG" + ca_ttl = "3m" + ca_subject { + country = ["US"] + organization = ["SPIFFE"] + common_name = "" + } +} + +plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "./one.org/spire/.data/server/datastore.sqlite3" + } + } + + NodeAttestor "join_token" { + plugin_data { + } + } + + KeyManager "memory" { + plugin_data = {} + } +} diff --git a/demo/two.org/harvester/harvester.conf b/demo/two.org/harvester/harvester.conf new file mode 100644 index 00000000..6ebe3353 --- /dev/null +++ b/demo/two.org/harvester/harvester.conf @@ -0,0 +1,5 @@ +harvester { + spire_socket_path = "/tmp/two.org/spire-server/private/api.sock" + server_address = "localhost:8085" + bundle_updates_interval = "5s" +} diff --git a/demo/two.org/spire/conf/agent/agent.conf b/demo/two.org/spire/conf/agent/agent.conf new file mode 100644 index 00000000..921b92dd --- /dev/null +++ b/demo/two.org/spire/conf/agent/agent.conf @@ -0,0 +1,25 @@ +agent { + data_dir = "./two.org/spire/.data/agent" + log_level = "DEBUG" + server_address = "127.0.0.1" + server_port = "8082" + socket_path = "/tmp/two.org/spire-agent/private/api.sock" + trust_bundle_path = "./two.org/spire/conf/agent/root_ca.crt" + trust_domain = "two.org" +} + +plugins { + NodeAttestor "join_token" { + plugin_data { + } + } + KeyManager "disk" { + plugin_data { + directory = "./two.org/spire/.data/agent" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } +} diff --git a/demo/two.org/spire/conf/server/server.conf b/demo/two.org/spire/conf/server/server.conf new file mode 100644 index 00000000..39d0702f --- /dev/null +++ b/demo/two.org/spire/conf/server/server.conf @@ -0,0 +1,32 @@ +server { + bind_address = "127.0.0.1" + bind_port = "8082" + socket_path = "/tmp/two.org/spire-server/private/api.sock" + trust_domain = "two.org" + data_dir = "./two.org/spire/.data/server" + log_level = "DEBUG" + ca_ttl = "3m" + ca_subject { + country = ["US"] + organization = ["SPIFFE"] + common_name = "" + } +} + +plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "./two.org/spire/.data/server/datastore.sqlite3" + } + } + + NodeAttestor "join_token" { + plugin_data { + } + } + + KeyManager "memory" { + plugin_data = {} + } +} diff --git a/demo/util.sh b/demo/util.sh new file mode 100644 index 00000000..3adc0a66 --- /dev/null +++ b/demo/util.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +cyan=$(tput setaf 6) +yellow=$(tput setaf 3) +green=$(tput setaf 2) +purple=$(tput setaf 5) +norm=$(tput sgr0) + +one() { + colorize $cyan "ONE.ORG" ${@:1} +} + +two() { + colorize $yellow "TWO.ORG" ${@:1} +} + +server() { + colorize $green "SERVER" ${@:1} +} + +client() { + colorize $purple "CLIENT" ${@:1} +} + +# Usage: colorize +colorize() { + color=$1 + prefix=$2 + cmd=${@:3} + eval "${cmd} | sed -e 's/^/${color}[${prefix}] /' -e 's/$/${norm}/' &" +} diff --git a/demo/x-cleanup.sh b/demo/x-cleanup.sh new file mode 100755 index 00000000..f81e1d2d --- /dev/null +++ b/demo/x-cleanup.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +rm -rf ./one.org/spire/.data +rm -rf ./two.org/spire/.data +rm -rf /tmp/one.org +rm -rf /tmp/two.org