├── 5_run_kubernetes.sh ├── deployment ├── webecho │ ├── src │ │ ├── echo-server │ │ │ ├── go.mod │ │ │ └── main.go │ │ └── web-server │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── css.go │ │ │ └── main.go │ ├── docker │ │ ├── web │ │ │ └── Dockerfile │ │ └── echo │ │ │ └── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ └── build.sh └── k8s_yamls │ ├── load_images.sh │ ├── deploy_all.sh │ ├── gateway.yaml │ └── deploy.yaml ├── patch ├── volumes.txt ├── volumeMounts.txt └── patch.py ├── kind ├── start_kind.sh └── kind-sds-conf.yaml ├── 1_install_apt_dependencies.sh ├── .github └── dco.yml ├── .gitignore ├── MAINTAINERS.md ├── docker ├── Dockerfile └── build_proxy_image.sh ├── 4_download_istio.sh ├── spire ├── setup_spire_echo.sh ├── setup_spire_web.sh ├── add_spire_entry.sh ├── setup_spire.sh └── deploy_spire.sh ├── 2_download_kind.sh ├── 3_download_kubectl.sh ├── 6_download_and_run_spire.sh ├── 8_install_workload.sh ├── CONTRIBUTING.md ├── 7_patch_and_run_istio.sh ├── README.md └── LICENSE /5_run_kubernetes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd kind 4 | ./start_kind.sh 5 | -------------------------------------------------------------------------------- /deployment/webecho/src/echo-server/go.mod: -------------------------------------------------------------------------------- 1 | module echo-server 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /patch/volumes.txt: -------------------------------------------------------------------------------- 1 | - name: spire-agent-socket-dir 2 | hostPath: 3 | path: /run/spire/sockets 4 | -------------------------------------------------------------------------------- /kind/start_kind.sh: -------------------------------------------------------------------------------- 1 | ../bin/kind delete cluster 2 | ../bin/kind create cluster --config kind-sds-conf.yaml 3 | -------------------------------------------------------------------------------- /1_install_apt_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo apt-get install -y curl docker.io python docker-compose 4 | -------------------------------------------------------------------------------- /patch/volumeMounts.txt: -------------------------------------------------------------------------------- 1 | - mountPath: /spire-agent-socket-dir 2 | name: spire-agent-socket-dir 3 | readOnly: false 4 | -------------------------------------------------------------------------------- /deployment/webecho/src/web-server/go.mod: -------------------------------------------------------------------------------- 1 | module web-server 2 | 3 | require github.com/go-chi/chi v3.3.3+incompatible 4 | 5 | go 1.13 6 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | # This enables DCO bot for you, please take a look https://github.com/probot/dco 2 | # for more details. 3 | require: 4 | members: false 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | istio-1.5.4/ 3 | spire/spire-tutorials/ 4 | istio_patch/ 5 | deployment/webecho/docker/echo/echo-server 6 | deployment/webecho/docker/web/web-server 7 | go/ 8 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # MAINTAINERS 2 | 3 | Doron Chen - cdoron@il.ibm.com 4 | Roee Shlomo - Roee.Shlomo@ibm.com 5 | Tomer Solomon - Tomer.Solomon@ibm.com 6 | Ronen Kat - ronenkat@il.ibm.com 7 | -------------------------------------------------------------------------------- /deployment/webecho/docker/web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM envoyproxy/envoy-alpine:v1.10.0 2 | COPY web-server /usr/local/bin/web-server 3 | CMD web-server -log /tmp/web-server.log 4 | # CMD sleep 7d 5 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/istio/proxyv2:1.5.4 2 | 3 | COPY envoy_bootstrap_v2.json /var/lib/istio/envoy/envoy_bootstrap_tmpl.json 4 | 5 | ENTRYPOINT ["/usr/local/bin/pilot-agent"] 6 | -------------------------------------------------------------------------------- /4_download_istio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -d istio-1.5.4 ] 4 | then exit 0 5 | fi 6 | 7 | echo downloading istio 8 | 9 | curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.5.4 sh - 10 | -------------------------------------------------------------------------------- /deployment/webecho/docker/echo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM envoyproxy/envoy-alpine:v1.10.0 2 | COPY echo-server /usr/local/bin/echo-server 3 | CMD /usr/local/bin/echo-server -log /tmp/echo-server.log 4 | # CMD sleep 7d 5 | -------------------------------------------------------------------------------- /spire/setup_spire_echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 3 | export PATH=$PATH:$DIR:$DIR/../bin 4 | 5 | add_spire_entry.sh default serviceaccount-echo 6 | -------------------------------------------------------------------------------- /spire/setup_spire_web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 3 | export PATH=$PATH:$DIR:$DIR/../bin 4 | 5 | add_spire_entry.sh default serviceaccount-web 6 | -------------------------------------------------------------------------------- /deployment/webecho/README.md: -------------------------------------------------------------------------------- 1 | # Echo and Web Servers - Sample Workload 2 | 3 | This workload is based on a workload from the [spire-example](https://github.com/spiffe/spire-examples/tree/master/examples/envoy) repository. 4 | -------------------------------------------------------------------------------- /deployment/webecho/src/web-server/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8= 2 | github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 3 | -------------------------------------------------------------------------------- /2_download_kind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f bin/kind ] 4 | then exit 0 5 | fi 6 | 7 | echo downloading kind 8 | 9 | mkdir -p bin 10 | curl -Lo ./bin/kind https://kind.sigs.k8s.io/dl/v0.8.1/kind-$(uname)-amd64 11 | chmod +x ./bin/kind 12 | -------------------------------------------------------------------------------- /deployment/k8s_yamls/load_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | export PATH=$PATH:$DIR/../../bin 5 | 6 | kind load docker-image webecho_web:latest 7 | kind load docker-image webecho_echo:latest 8 | -------------------------------------------------------------------------------- /deployment/webecho/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | echo: 4 | build: ./docker/echo 5 | hostname: echo 6 | tty: true 7 | privileged: true 8 | web: 9 | build: ./docker/web 10 | hostname: web 11 | tty: true 12 | privileged: true 13 | links: 14 | - echo 15 | -------------------------------------------------------------------------------- /3_download_kubectl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f bin/kubectl ] 4 | then exit 0 5 | fi 6 | 7 | echo downloading kubectl 8 | 9 | mkdir -p bin 10 | curl -Lo ./bin/kubectl https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl 11 | chmod +x ./bin/kubectl 12 | -------------------------------------------------------------------------------- /kind/kind-sds-conf.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.sigs.k8s.io/v1alpha3 3 | kubeadmConfigPatches: 4 | - | 5 | apiVersion: kubeadm.k8s.io/v1beta2 6 | kind: ClusterConfiguration 7 | metadata: 8 | name: config 9 | apiServer: 10 | extraArgs: 11 | "service-account-issuer": "kubernetes.default.svc" 12 | "service-account-signing-key-file": "/etc/kubernetes/pki/sa.key" 13 | -------------------------------------------------------------------------------- /deployment/webecho/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | 7 | export GOROOT=$DIR/../../go 8 | export PATH=$PATH:$DIR/../../go/bin 9 | 10 | (cd src/web-server && GOOS=linux go build -v -o $DIR/docker/web/web-server) 11 | (cd src/echo-server && GOOS=linux go build -v -o $DIR/docker/echo/echo-server) 12 | 13 | docker-compose -f docker-compose.yml build 14 | -------------------------------------------------------------------------------- /spire/add_spire_entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 2 ]; then 4 | echo "Usage: $0 [ns] [sa]" 5 | exit 6 | fi 7 | 8 | export PATH=$PATH:../bin 9 | 10 | kubectl exec -n spire spire-server-0 -- \ 11 | /opt/spire/bin/spire-server entry create \ 12 | -spiffeID spiffe://example.org/ns/$1/sa/$2 \ 13 | -parentID spiffe://example.org/ns/spire/sa/spire-agent \ 14 | -selector k8s:ns:$1 \ 15 | -selector k8s:sa:$2 16 | -------------------------------------------------------------------------------- /6_download_and_run_spire.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | export PATH=$PATH:$DIR/bin 5 | 6 | cd spire 7 | 8 | if [ ! -d spire-tutorials ] 9 | then git clone https://github.com/spiffe/spire-tutorials/ 10 | sed -i 's/gcr.io\/spiffe-io\/spire-agent:.*/gcr.io\/spiffe-io\/spire-agent:1afb9bfd8fb4180d761e8c72de9e4740762ea1e7/g' spire-tutorials/k8s/quickstart/agent-daemonset.yaml 11 | fi 12 | 13 | ./deploy_spire.sh 14 | -------------------------------------------------------------------------------- /deployment/k8s_yamls/deploy_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | export PATH=$PATH:$DIR/../../bin:$DIR/../../istio-1.5.4/bin 5 | 6 | kubectl delete service/echo service/web deployment.apps/echo-v1 deployment.apps/web-v1 7 | 8 | kubectl apply -f deploy.yaml -f gateway.yaml 9 | 10 | kubectl get deployment web-v1 -o yaml | istioctl kube-inject -f - | kubectl apply -f - 11 | kubectl get deployment echo-v1 -o yaml | istioctl kube-inject -f - | kubectl apply -f - 12 | -------------------------------------------------------------------------------- /docker/build_proxy_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | export PATH=$PATH:$DIR:$DIR/../bin 5 | 6 | cd $DIR 7 | wget https://raw.githubusercontent.com/istio/istio/1.5.4/tools/packaging/common/envoy_bootstrap_v2.json 8 | sed -i 's/"\/etc\/istio\/proxy\/SDS"/"\/spire-agent-socket-dir\/agent.sock"/g' envoy_bootstrap_v2.json 9 | 10 | docker build --tag docker.io/istio/proxyv2-spire:1.5.4 . 11 | rm -f envoy_bootstrap_v2.json 12 | 13 | kind load docker-image docker.io/istio/proxyv2-spire:1.5.4 14 | -------------------------------------------------------------------------------- /spire/setup_spire.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | export PATH=$PATH:$DIR:$DIR/../bin 5 | 6 | kubectl exec -n spire spire-server-0 -- \ 7 | /opt/spire/bin/spire-server entry create \ 8 | -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \ 9 | -selector k8s_sat:cluster:demo-cluster \ 10 | -selector k8s_sat:agent_ns:spire \ 11 | -selector k8s_sat:agent_sa:spire-agent \ 12 | -node 13 | 14 | add_spire_entry.sh istio-system istio-ingressgateway-service-account 15 | add_spire_entry.sh istio-system prometheus 16 | -------------------------------------------------------------------------------- /deployment/k8s_yamls/gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: Gateway 3 | metadata: 4 | name: demo-gateway 5 | spec: 6 | selector: 7 | istio: ingressgateway # use istio default controller 8 | servers: 9 | - port: 10 | number: 80 11 | name: http 12 | protocol: HTTP 13 | hosts: 14 | - "*" 15 | --- 16 | apiVersion: networking.istio.io/v1alpha3 17 | kind: VirtualService 18 | metadata: 19 | name: demo 20 | spec: 21 | hosts: 22 | - "*" 23 | gateways: 24 | - demo-gateway 25 | http: 26 | - route: 27 | - destination: 28 | host: web 29 | port: 30 | number: 9000 31 | -------------------------------------------------------------------------------- /8_install_workload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | export PATH=$PATH:$DIR/bin 5 | 6 | if [ ! -d go ] 7 | then wget https://dl.google.com/go/go1.14.3.linux-amd64.tar.gz 8 | tar xfz go1.14.3.linux-amd64.tar.gz 9 | rm go1.14.3.linux-amd64.tar.gz 10 | fi 11 | 12 | cd deployment/webecho 13 | ./build.sh 14 | 15 | cd ../k8s_yamls 16 | ./load_images.sh 17 | ./deploy_all.sh 18 | 19 | kubectl wait --for=condition=Ready pod -l app=web 20 | kubectl wait --for=condition=Ready pod -l app=echo 21 | 22 | INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}') 23 | INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}') 24 | 25 | GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT 26 | 27 | firefox $GATEWAY_URL & 28 | -------------------------------------------------------------------------------- /spire/deploy_spire.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export PATH=$PATH:../kind/bin 4 | 5 | kubectl delete namespace spire 6 | 7 | kubectl apply -f spire-tutorials/k8s/quickstart/spire-namespace.yaml 8 | 9 | kubectl apply \ 10 | -f spire-tutorials/k8s/quickstart/server-account.yaml \ 11 | -f spire-tutorials/k8s/quickstart/spire-bundle-configmap.yaml \ 12 | -f spire-tutorials/k8s/quickstart/server-cluster-role.yaml 13 | 14 | kubectl apply \ 15 | -f spire-tutorials/k8s/quickstart/server-configmap.yaml \ 16 | -f spire-tutorials/k8s/quickstart/server-statefulset.yaml \ 17 | -f spire-tutorials/k8s/quickstart/server-service.yaml 18 | 19 | kubectl apply \ 20 | -f spire-tutorials/k8s/quickstart/agent-account.yaml \ 21 | -f spire-tutorials/k8s/quickstart/agent-cluster-role.yaml 22 | 23 | kubectl apply \ 24 | -f spire-tutorials/k8s/quickstart/agent-configmap.yaml \ 25 | -f spire-tutorials/k8s/quickstart/agent-daemonset.yaml 26 | 27 | kubectl wait --for=condition=Ready pod/spire-server-0 -n spire 28 | kubectl wait --for=condition=Ready pod -l app=spire-agent -n spire 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing In General 2 | Our project welcomes external contributions. 3 | 4 | To contribute code or documentation, please submit a [pull request](https://github.com/ibm/istio-spire/pulls). 5 | 6 | ## Legal 7 | 8 | Each source file must include a license header for the Apache 9 | Software License 2.0. Using the SPDX format is the simplest approach. 10 | e.g. 11 | 12 | ``` 13 | /* 14 | Copyright All Rights Reserved. 15 | 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | ``` 19 | 20 | We have tried to make it as easy as possible to make contributions. This 21 | applies to how we handle the legal aspects of contribution. We use the 22 | same approach - the [Developer's Certificate of Origin 1.1 (DCO)](https://github.com/hyperledger/fabric/blob/master/docs/source/DCO1.1.txt) - that the Linux® Kernel [community](https://elinux.org/Developer_Certificate_Of_Origin) 23 | uses to manage code contributions. 24 | 25 | We simply ask that when submitting a patch for review, the developer 26 | must include a sign-off statement in the commit message. 27 | 28 | Here is an example Signed-off-by line, which indicates that the 29 | submitter accepts the DCO: 30 | 31 | ``` 32 | Signed-off-by: John Doe 33 | ``` 34 | 35 | You can include this automatically when you commit a change to your 36 | local git repository using the following command: 37 | 38 | ``` 39 | git commit -s 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /7_patch_and_run_istio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | export PATH=$PATH:$DIR/istio-1.5.4/bin/:$DIR/bin:$DIR/spire 5 | 6 | TMPFILE=/tmp/tmp.tmp 7 | 8 | if [ ! -d istio_patch ] 9 | then cp -R istio-1.5.4/install/kubernetes/operator/ istio_patch 10 | 11 | for file in `find istio_patch -name "*.yaml" | xargs grep -l "volumes:"` 12 | do echo $file 13 | ./patch/patch.py $file ./patch/volumes.txt ./patch/volumeMounts.txt > $TMPFILE 14 | mv $TMPFILE $file 15 | done 16 | 17 | for file in `find istio_patch -name "*.yaml" | xargs grep -l "proxyv2"` 18 | do echo $file 19 | sed -i 's/proxyv2/proxyv2-spire/g' $file 20 | done 21 | 22 | # sed -i 's/proxyv2/proxyv2-spire/g' istio_patch/profiles/default.yaml 23 | sed -i 's/cluster.local/example.org/g' istio_patch/profiles/default.yaml 24 | sed -i 's/imagePullPolicy: ""/imagePullPolicy: "IfNotPresent"/g' istio_patch/profiles/default.yaml 25 | fi 26 | 27 | cd docker 28 | ./build_proxy_image.sh 29 | 30 | cd .. 31 | 32 | echo Configuring Spiffe identities for Istio services 33 | ./spire/setup_spire.sh 34 | 35 | echo Running Istio 36 | istioctl manifest apply --set installPackagePath=./istio_patch --set profile=./istio_patch/profiles/default.yaml 37 | 38 | kubectl wait --for=condition=Ready pod -l app=istio-ingressgateway -n istio-system 39 | kubectl wait --for=condition=Ready pod -l app=istiod -n istio-system 40 | kubectl wait --for=condition=Ready pod -l app=prometheus -n istio-system 41 | -------------------------------------------------------------------------------- /patch/patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | import sys 4 | 5 | def print_patch_lines(filename, k): 6 | f = open(filename, 'r') 7 | for line in f.readlines(): 8 | print " " * k + line.rstrip() 9 | f.close() 10 | 11 | def main(argv): 12 | if len(argv) != 4: 13 | print "Usage: " + argv[0] + " yaml_file_name volumes_patch_file volume_mount_patch_file" 14 | sys.exit(1) 15 | 16 | f = open(argv[1], 'r') 17 | lines = f.readlines() 18 | 19 | i = 0 20 | while i < len(lines): 21 | line = lines[i] 22 | k1 = line.find("volumes:") 23 | k2 = line.find("volumeMounts:") 24 | if k1 != -1: 25 | print line.rstrip() 26 | i = i + 1 27 | line = lines[i] 28 | buf = [] 29 | while line.lstrip()[0] != '-': 30 | buf.append(line.rstrip()) 31 | i = i + 1 32 | line = lines[i] 33 | k1 = len(line) - len(line.lstrip(' ')) 34 | buf.append(line.rstrip()) 35 | print_patch_lines(argv[2], k1) 36 | for b in buf: 37 | print b 38 | elif k2 != -1: 39 | print line.rstrip() 40 | i = i + 1 41 | line = lines[i] 42 | buf = [] 43 | while line.lstrip()[0] != '-': 44 | buf.append(line.rstrip()) 45 | i = i + 1 46 | line = lines[i] 47 | k2 = len(line) - len(line.lstrip(' ')) 48 | buf.append(line.rstrip()) 49 | print_patch_lines(argv[3], k2) 50 | for b in buf: 51 | print b 52 | else: 53 | print line.rstrip() 54 | i = i + 1 55 | 56 | f.close() 57 | 58 | if __name__ == "__main__": 59 | main(sys.argv) 60 | -------------------------------------------------------------------------------- /deployment/webecho/src/echo-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/http" 11 | "os" 12 | ) 13 | 14 | var ( 15 | addrFlag = flag.String("addr", ":8081", "address to bind the echo server to") 16 | logFlag = flag.String("log", "", "path to log to (empty=stderr)") 17 | ) 18 | 19 | func handler(w http.ResponseWriter, r *http.Request) { 20 | log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL) 21 | responseBytes, err := json.Marshal(r.Header) 22 | if err != nil { 23 | http.Error(w, "unable to marshal response", http.StatusInternalServerError) 24 | return 25 | } 26 | 27 | w.Header().Set("Content-Type", "application/json") 28 | w.Header().Set("Connection", "close") 29 | w.Write(responseBytes) 30 | } 31 | 32 | func main() { 33 | if err := run(context.Background()); err != nil { 34 | fmt.Fprintf(os.Stderr, "%+v\n", err) 35 | os.Exit(1) 36 | } 37 | } 38 | 39 | func run(ctx context.Context) (err error) { 40 | flag.Parse() 41 | log.SetPrefix("echo> ") 42 | log.SetFlags(log.Ltime) 43 | if *logFlag != "" { 44 | logFile, err := os.OpenFile(*logFlag, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 45 | if err != nil { 46 | return fmt.Errorf("unable to open log file: %v", err) 47 | } 48 | defer logFile.Close() 49 | log.SetOutput(logFile) 50 | } else { 51 | log.SetOutput(os.Stdout) 52 | } 53 | log.Printf("starting echo server...") 54 | 55 | ln, err := net.Listen("tcp", *addrFlag) 56 | if err != nil { 57 | return fmt.Errorf("unable to listen: %v", err) 58 | } 59 | defer ln.Close() 60 | 61 | log.Printf("listening on %s...", ln.Addr()) 62 | server := &http.Server{ 63 | Handler: http.HandlerFunc(handler), 64 | } 65 | return server.Serve(ln) 66 | } 67 | -------------------------------------------------------------------------------- /deployment/webecho/src/web-server/css.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func serveCSS(w http.ResponseWriter, r *http.Request) { 6 | w.Header().Set("Content-Type", "text/css") 7 | w.Write([]byte(` 8 | body {background-color: 999999;} 9 | button { 10 | -webkit-transition-duration: 0.4s; /* Safari */ 11 | transition-duration: 0.4s; 12 | background-color: #333333; 13 | background-color: #02bdd9; 14 | border-radius: 8px; 15 | border: none; 16 | color: white; 17 | padding: 15px 32px; 18 | text-align: center; 19 | text-decoration: none; 20 | display: inline-block; 21 | float: none; 22 | font-size: 24px; 23 | font-weight: bold; 24 | text-shadow: 1px 1px 1px #000000; 25 | width: 40%; 26 | } 27 | button:hover { 28 | background-color: #029db9; 29 | color: white; 30 | } 31 | .footer { 32 | position: fixed; 33 | left: 0; 34 | bottom: 0; 35 | height: 40px; 36 | padding-left: 12px; 37 | line-height: 40px; 38 | width: 100%; 39 | background-color: #333333; 40 | color: white; 41 | text-align: left; 42 | } 43 | .error-text{ 44 | font-family: monospace; 45 | font-size: 24px; 46 | white-space: pre-wrap; 47 | hyphens: auto; 48 | word-break: break-word; 49 | word-wrap: break-word; 50 | overflow-wrap: break-word; 51 | } 52 | .header-title { 53 | font-size: 24px; 54 | } 55 | .header-key { 56 | font-family: monospace; 57 | font-size: 24px; 58 | white-space: pre; 59 | } 60 | .header-value { 61 | font-family: monospace; 62 | font-size: 24px; 63 | white-space: pre-wrap; /* css-3 */ 64 | hyphens: auto; 65 | word-break: break-all; 66 | word-wrap: break-word; 67 | overflow-wrap: break-word; 68 | } 69 | .header-grid-container { 70 | display: inline-grid; 71 | grid-template-columns: min-content auto; 72 | } 73 | .button-grid-container { 74 | width: 100%; 75 | display: inline-grid; 76 | grid-gap: 10px; 77 | } 78 | #success { 79 | background-color: #bcd819; 80 | } 81 | #error { 82 | background-color: #e32d31; 83 | color: white; 84 | } 85 | .card { 86 | /* Add shadows to create the "card" effect */ 87 | box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); 88 | transition: 0.3s; 89 | width: 100%; 90 | } 91 | 92 | /* On mouse-over, add a deeper shadow */ 93 | .card:hover { 94 | box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); 95 | } 96 | 97 | /* Add some padding inside the card container */ 98 | .container { 99 | padding: 2px 16px; 100 | } 101 | `)) 102 | } 103 | -------------------------------------------------------------------------------- /deployment/k8s_yamls/deploy.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # Web service 3 | ################################################################################################## 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: web 8 | labels: 9 | app: web 10 | service: web 11 | spec: 12 | ports: 13 | - port: 9000 14 | name: http 15 | selector: 16 | app: web 17 | --- 18 | apiVersion: v1 19 | kind: ServiceAccount 20 | metadata: 21 | name: serviceaccount-web 22 | labels: 23 | account: web 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: web-v1 29 | labels: 30 | app: web 31 | version: v1 32 | spec: 33 | replicas: 1 34 | selector: 35 | matchLabels: 36 | app: web 37 | version: v1 38 | template: 39 | metadata: 40 | labels: 41 | app: web 42 | version: v1 43 | spec: 44 | serviceAccountName: serviceaccount-web 45 | containers: 46 | - name: web 47 | image: webecho_web:latest 48 | imagePullPolicy: Never 49 | ports: 50 | - containerPort: 9000 51 | --- 52 | ################################################################################################## 53 | # Echo service 54 | ################################################################################################## 55 | apiVersion: v1 56 | kind: Service 57 | metadata: 58 | name: echo 59 | labels: 60 | app: echo 61 | service: echo 62 | spec: 63 | ports: 64 | - name: http 65 | port: 8081 66 | targetPort: 8081 67 | - name: listener8001 68 | port: 8001 69 | targetPort: 8001 70 | - name: listener8002 71 | port: 8002 72 | targetPort: 8002 73 | selector: 74 | app: echo 75 | --- 76 | apiVersion: v1 77 | kind: ServiceAccount 78 | metadata: 79 | name: serviceaccount-echo 80 | labels: 81 | account: echo 82 | --- 83 | apiVersion: apps/v1 84 | kind: Deployment 85 | metadata: 86 | name: echo-v1 87 | labels: 88 | app: echo 89 | version: v1 90 | spec: 91 | replicas: 1 92 | selector: 93 | matchLabels: 94 | app: echo 95 | version: v1 96 | template: 97 | metadata: 98 | labels: 99 | app: echo 100 | version: v1 101 | spec: 102 | serviceAccountName: serviceaccount-echo 103 | containers: 104 | - name: echo 105 | image: webecho_echo:latest 106 | imagePullPolicy: Never 107 | ports: 108 | - name: port8081 109 | containerPort: 8081 110 | - name: port8001 111 | containerPort: 8001 112 | - name: port8002 113 | containerPort: 8002 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Istio Identities with SPIFFE/SPIRE 2 | 3 | The scripts in this repository demonstrate how to replace the identity-issuing mechanism of Istio with that of Spire. 4 | 5 | This repository was tested on Ubuntu 18.04.4 LTS, on a VirtualBox VM. 6 | You will need at least two CPUs to successfully run Istio. We use **Istio 1.5.4**. 7 | 8 | We hope that this work would inspire a complete solution to installing Istio with Spire, and we welcome contributions to this effort. 9 | 10 | ## Replacing the Istio Identity-Issuing Mechanism with that of Spire 11 | 12 | **Step 1:** Install Dependencies (you need sudo permissions) 13 | > ./1_install_apt_dependencies.sh 14 | 15 | After running this script, run *docker ps* to check whether docker is running properly for your user. If not, try adding permissions to */var/run/docker.sock*: 16 | > sudo chmod 777 /var/run/docker.sock 17 | 18 | **Step 2:** Download Kubernetes Kind 19 | > ./2_download_kind.sh 20 | 21 | **Step 3:** Download kubectl 22 | > ./3_download_kubectl.sh 23 | 24 | **Step 4:** Download Istio 1.5.4 25 | > ./4_download_istio.sh 26 | 27 | **Step 5:** Run Kubernetes 28 | > ./5_run_kubernetes.sh 29 | 30 | **Step 6:** Run Spire on Kubernetes 31 | > ./6_download_and_run_spire.sh 32 | 33 | **Step 7:** Patch Istio Configuration, and run Istio 34 | > ./7_patch_and_run_istio.sh 35 | 36 | This script does the following: 37 | 1. Copies the default Istio configuration directory to a new *istio_patch* directory 38 | 1. Patches the Istio configuration to mount the Spire Unix Domain Socket Directory onto Istio containers 39 | 1. Make minor change to the Istio proxy container image 40 | 1. Create Spire enties for the Prometheus and Ingress Gateway Istio services 41 | 1. Run Istio and wait for all Istio services to come up 42 | 43 | **Step 8:** Run a sample workload - a Web server and an Echo server 44 | 45 | This workload is based on a workload from the [spire-example](https://github.com/spiffe/spire-examples/tree/master/examples/envoy) repository. 46 | > ./8_install_workload.sh 47 | 48 | This script compiles the code for the web and echo servers, and then deploys them on Istio. Finally it runs firefox on the web server page. 49 | 50 | On the web browser you will see the following message: 51 | > upstream connect error or disconnect/reset before headers. reset reason: connection failure 52 | 53 | Why is that? Because **neither the web server nor the echo server have an identity yet**, and can therefore not receive any communications. In order to fix that, first run: 54 | > ./spire/setup_spire_web.sh 55 | 56 | Refresh the page on the browser. You will see the *Send to Echo Server* button. If you press it you will get an error message, because the echo server does not yet have an identity. Now run: 57 | > ./spire/setup_spire_echo.sh 58 | 59 | If you press the button again, you will see the message that was returned from the echo server. 60 | 61 | ## Notes 62 | 63 | If you have any questions or issues you can create a new [issue here][issues]. 64 | 65 | Pull requests are very welcome! Make sure your patches are well tested. 66 | Ideally create a topic branch for every separate change you make. For 67 | example: 68 | 69 | 1. Fork the repo 70 | 2. Create your feature branch (`git checkout -b my-new-feature`) 71 | 3. Commit your changes (`git commit -am 'Added some feature'`) 72 | 4. Push to the branch (`git push origin my-new-feature`) 73 | 5. Create new Pull Request 74 | 75 | 76 | ## License & Authors 77 | 78 | If you would like to see the detailed LICENSE click [here](LICENSE). 79 | 80 | ```text 81 | Copyright:: 2019- IBM, Inc 82 | 83 | Licensed under the Apache License, Version 2.0 (the "License"); 84 | you may not use this file except in compliance with the License. 85 | You may obtain a copy of the License at 86 | 87 | http://www.apache.org/licenses/LICENSE-2.0 88 | 89 | Unless required by applicable law or agreed to in writing, software 90 | distributed under the License is distributed on an "AS IS" BASIS, 91 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 92 | See the License for the specific language governing permissions and 93 | limitations under the License. 94 | ``` 95 | 96 | 97 | [issues]: https://github.com/IBM/istio-spire/issues/new 98 | -------------------------------------------------------------------------------- /deployment/webecho/src/web-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "html/template" 9 | "io" 10 | "log" 11 | "net" 12 | "net/http" 13 | "os" 14 | "sync/atomic" 15 | "time" 16 | 17 | "github.com/go-chi/chi" 18 | ) 19 | 20 | var ( 21 | addrFlag = flag.String("addr", ":9000", "address to bind the web server to") 22 | directFlag = flag.String("direct", "echo:8081", "address to the echo server") 23 | envoyMTLSFlag = flag.String("envoy-to-envoy-mtls", "0.0.0.0:8001", "address to the envoy-to-envoy mTLS endpoint") 24 | envoyTLSFlag = flag.String("envoy-to-envoy-tls", "0.0.0.0:8002", "address to the envoy-to-envoy TLS endpoint") 25 | logFlag = flag.String("log", "", "path to log to (empty=stderr)") 26 | 27 | pageHTML = template.Must(template.New("").Parse(` 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 | {{- if .WebHeader }} 39 |
40 |
41 |
42 |

Headers Sent by Web Server

43 |
44 | {{- range $k, $v := .WebHeader }} 45 |
46 |
{{ printf "%-30s: " $k }}
47 |
48 |
49 |
{{- range $i, $v := $v }}{{ if $i }},{{ end }}{{ $v }}{{ end }}
50 |
51 | {{- end }} 52 |
53 |
54 |
55 |
56 | {{- end }} 57 | 58 | {{- if .EchoHeader }} 59 |
60 |
61 |
62 |

Headers Received by Echo Server

63 |
64 | {{- range $k, $v := .EchoHeader }} 65 |
66 |
{{ printf "%-30s: " $k }}
67 |
68 |
69 |
{{- range $i, $v := $v }}{{ if $i }},{{ end }}{{ $v }}{{ end }}
70 |
71 | {{- end }} 72 |
73 |
74 |
75 |
76 | {{- end }} 77 | 78 | {{- if .Error }} 79 |
80 |
81 |
82 |

Error:

83 |
{{ .Error }}
84 |
85 |
86 |
87 | {{- end }} 88 | {{- if .Attempt }} 89 | 90 | {{- end }} 91 | 92 | 93 | `)) 94 | attempt int32 = 0 95 | ) 96 | 97 | func serveRoot(w http.ResponseWriter, r *http.Request) { 98 | if r.URL.Path != "/" { 99 | http.NotFound(w, r) 100 | return 101 | } 102 | 103 | var host string 104 | route := r.URL.Query().Get("route") 105 | switch route { 106 | case "": 107 | pageHTML.Execute(w, successParams(0, nil, nil)) 108 | return 109 | case "direct": 110 | host = *directFlag 111 | case "envoy-to-envoy-mtls": 112 | host = *envoyMTLSFlag 113 | case "envoy-to-envoy-tls": 114 | host = *envoyTLSFlag 115 | default: 116 | pageHTML.Execute(w, errorParams(attempt, nil, "invalid route value %q", route)) 117 | return 118 | } 119 | 120 | echoURL := fmt.Sprintf("http://%s", host) 121 | 122 | atomic.AddInt32(&attempt, 1) 123 | w.Header().Set("Content-Type", "text/html") 124 | 125 | req, err := http.NewRequest("GET", echoURL, nil) 126 | if err != nil { 127 | log.Printf("[%s] failed to create request for echo server %q: %v", r.RemoteAddr, route, err) 128 | pageHTML.Execute(w, errorParams(attempt, nil, "failed to create request: %v", err)) 129 | return 130 | } 131 | req.Header.Set("Date", httpDate(time.Now())) 132 | req.Header.Set("User-Agent", "") 133 | req.Header.Set("X-Super-Secret-Password", "hunter2") 134 | 135 | log.Printf("[%s] Issuing GET %s", r.RemoteAddr, echoURL) 136 | resp, err := http.DefaultClient.Do(req) 137 | req.Header.Del("User-Agent") 138 | if err != nil { 139 | log.Printf("[%s] failed to send request to echo server %q: %v", r.RemoteAddr, route, err) 140 | pageHTML.Execute(w, errorParams(attempt, req.Header, "failed to contact echo server: %v", err)) 141 | return 142 | } 143 | defer resp.Body.Close() 144 | log.Printf("[%s] GOT %s", r.RemoteAddr, echoURL) 145 | 146 | if resp.StatusCode != http.StatusOK { 147 | body := tryRead(resp.Body) 148 | log.Printf("[%s] unexpected echo server %q response: %d\n%s", r.RemoteAddr, route, resp.StatusCode, body) 149 | pageHTML.Execute(w, errorParams(attempt, req.Header, "unexpected echo server response status: %d\n\n%s", resp.StatusCode, body)) 150 | return 151 | } 152 | 153 | var header http.Header 154 | if err := json.NewDecoder(resp.Body).Decode(&header); err != nil { 155 | log.Printf("[%s] failed to parse echo server %q response: %v", r.RemoteAddr, route, err) 156 | pageHTML.Execute(w, errorParams(attempt, req.Header, "failed to parse echo server response: %v", err)) 157 | return 158 | } 159 | 160 | header.Del("Accept-Encoding") 161 | 162 | log.Printf("[%s] echo server %q response OK", r.RemoteAddr, route) 163 | pageHTML.Execute(w, successParams(attempt, req.Header, header)) 164 | } 165 | 166 | func successParams(attempt int32, webHeader, echoHeader http.Header) map[string]interface{} { 167 | return map[string]interface{}{ 168 | "Attempt": attempt, 169 | "WebHeader": webHeader, 170 | "EchoHeader": echoHeader, 171 | } 172 | } 173 | 174 | func errorParams(attempt int32, webHeader http.Header, format string, args ...interface{}) map[string]interface{} { 175 | return map[string]interface{}{ 176 | "Attempt": attempt, 177 | "WebHeader": webHeader, 178 | "Error": fmt.Sprintf(format, args...), 179 | } 180 | } 181 | 182 | func tryRead(r io.Reader) string { 183 | b := make([]byte, 1024) 184 | n, _ := r.Read(b) 185 | return string(b[:n]) 186 | } 187 | 188 | func main() { 189 | if err := run(context.Background()); err != nil { 190 | fmt.Fprintf(os.Stderr, "%+v\n", err) 191 | os.Exit(1) 192 | } 193 | } 194 | 195 | func run(ctx context.Context) (err error) { 196 | flag.Parse() 197 | log.SetPrefix("web> ") 198 | log.SetFlags(log.Ltime) 199 | if *logFlag != "" { 200 | logFile, err := os.OpenFile(*logFlag, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 201 | if err != nil { 202 | return fmt.Errorf("unable to open log file: %v", err) 203 | } 204 | defer logFile.Close() 205 | log.SetOutput(logFile) 206 | } else { 207 | log.SetOutput(os.Stdout) 208 | } 209 | log.Printf("starting web server...") 210 | 211 | ln, err := net.Listen("tcp", *addrFlag) 212 | if err != nil { 213 | return fmt.Errorf("unable to listen: %v", err) 214 | } 215 | defer ln.Close() 216 | 217 | r := chi.NewRouter() 218 | r.Use(noCache) 219 | r.Get("/styles.css", http.HandlerFunc(serveCSS)) 220 | r.Get("/", http.HandlerFunc(serveRoot)) 221 | 222 | log.Printf("listening on %s...", ln.Addr()) 223 | server := &http.Server{ 224 | Handler: r, 225 | } 226 | return server.Serve(ln) 227 | } 228 | 229 | func noCache(h http.Handler) http.Handler { 230 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 231 | w.Header().Set("Cache-Control", "no-cache") 232 | w.Header().Set("Expires", "0") 233 | h.ServeHTTP(w, r) 234 | }) 235 | } 236 | 237 | func httpDate(t time.Time) string { 238 | return t.UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") 239 | } 240 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------