Skip to content

Commit

Permalink
Upstream validation and activity logging (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikethecoder authored Mar 9, 2021
1 parent 26e0e93 commit ad79320
Show file tree
Hide file tree
Showing 15 changed files with 443 additions and 84 deletions.
22 changes: 11 additions & 11 deletions USER-JOURNEY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The gateway configuration can be hand-crafted or you can use a command line inte

**Simple Example**

```
``` bash
export NS="my_namespace"
export NAME="a-service-for-$NS"
echo "
Expand Down Expand Up @@ -65,7 +65,7 @@ services:
> **Upstream Services on OCP4:** If your service is running on OCP4, you should specify the Kubernetes Service in the `Service.host`. It must have the format: `<name>.<ocp-namespace>.svc` The Network Security Policies (NSP) will be setup automatically on the API Gateway side. You will need to create an NSP on your side looking something like this to allow the Gateway's test and prod environments to route traffic to your API:
```
``` yaml
kind: NetworkSecurityPolicy
apiVersion: security.devops.gov.bc.ca/v1alpha1
metadata:
Expand All @@ -84,7 +84,7 @@ spec:
> **Require mTLS between the Gateway and your Upstream Service?** To support mTLS on your Upstream Service, you will need to provide client certificate details and if you want to verify the upstream endpoint then the `ca_certificates` and `tls_verify` is required as well. An example:

```
``` yaml
services:
- name: my-upstream-service
host: my-upstream.site
Expand Down Expand Up @@ -116,7 +116,7 @@ Run: `gwa new` and follow the prompts.

Example:

```
``` bash
gwa new -o sample.yaml \
--route-host myapi.api.gov.bc.ca \
--service-url https://httpbin.org \
Expand All @@ -133,7 +133,7 @@ The Swagger console for the `gwa-api` can be used to publish Kong Gateway config

**Install (for Linux)**

```
``` bash
GWA_CLI_VERSION=v1.1.2; curl -L -O https://github.com/bcgov/gwa-cli/releases/download/${GWA_CLI_VERSION}/gwa_${GWA_CLI_VERSION}_linux_x64.zip
unzip gwa_${GWA_CLI_VERSION}_linux_x64.zip
./gwa --version
Expand All @@ -147,7 +147,7 @@ unzip gwa_${GWA_CLI_VERSION}_linux_x64.zip

Create a `.env` file and update the CLIENT_ID and CLIENT_SECRET with the new credentials that were generated in step #2:

```
``` bash
echo "
GWA_NAMESPACE=$NS
CLIENT_ID=<YOUR SERVICE ACCOUNT ID>
Expand All @@ -165,13 +165,13 @@ gwa init -T --namespace=$NS --client-id=<YOUR SERVICE ACCOUNT ID> --client-secre

**Publish**

```
``` bash
gwa pg sample.yaml
```

If you want to see the expected changes but not actually apply them, you can run:

```
``` bash
gwa pg --dry-run sample.yaml
```

Expand Down Expand Up @@ -211,7 +211,7 @@ To verify that the Gateway can access the upstream services, run the command: `g

In our test environment, the hosts that you defined in the routes get altered; to see the actual hosts, log into the <a href="https://gwa-apps-gov-bc-ca.test.apsgw.xyz/int" target="_blank">API Services Portal</a> and view the hosts under `Services`.

```
``` bash
curl https://${NAME}-api-gov-bc-ca.test.apsgw.xyz/headers
ab -n 20 -c 2 https://${NAME}-api-gov-bc-ca.test.apsgw.xyz/headers
Expand Down Expand Up @@ -243,7 +243,7 @@ The `acl` command provides a way to update the access for the namespace. It exp

For elevated privileges (such as managing Service Accounts), add the usernames to the `--managers` argument.

```
``` bash
gwa acl --users jjones@idir --managers acope@idir
```

Expand All @@ -265,7 +265,7 @@ Add a `.gwa` folder (can be called anything) that will be used to hold your gate

An example Github Workflow:

```
``` yaml
env:
NS: "<your namespace>"
Expand Down
41 changes: 41 additions & 0 deletions docs/SSL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

# SSL Termination

If you would like to verify the SSL endpoint, you can run the following two commands and compare the fingerprint and serial no.

```
export A_HOST=httpbin-regression.api.gov.bc.ca
openssl s_client -showcerts -verify 5 -connect 142.34.194.118:443 -servername ${A_HOST} < /dev/null | awk '/BEGIN/,/END/{ if(/BEGIN/){a++}; print}' > gw.crt
openssl x509 -in gw.crt -fingerprint -serial -dates -noout
```

## *.api.gov.bc.ca

| Issue Date | Expires | Deployed | SHA1 Fingerprint | Serial No. |
|-------------|-------------|-------------|-------------------------------------------------------------|----------------------------------|
| Oct 6 2020 | Oct 16 2021 | Oct 6 2020 | 20:7D:15:9D:42:BE:CC:BC:FD:EF:DF:13:77:C7:25:A3:A4:72:45:05 | 7876EB597E14F728C8455504177D3BC9 |
| Feb 16 2021 | Oct 16 2021 | Feb 25 2021 | 4D:EA:CE:C4:0A:73:67:D3:B4:03:F6:63:C4:E1:67:2C:47:9D:EA:82 | 3B5849D8A670251A3C20EA7859BDF996 |


You can run the above as one line:

```
A_HOST=httpbin-regression.api.gov.bc.ca; openssl s_client -showcerts -verify 5 -connect ${A_HOST}:443 -servername ${A_HOST} < /dev/null | awk '/BEGIN/,/END/{ if(/BEGIN/){a++}; print}' | openssl x509 -fingerprint -serial -dates -noout
```


**Individual File Verification**

```
openssl x509 -in data-api-wildcard-2020.crt -fingerprint -serial -dates -noout
openssl x509 -in data-api-wildcard-2021.crt -fingerprint -serial -dates -noout
```

**Cert/Key Verification**

```
openssl x509 -noout -modulus -in data-api-wildcard.crt | openssl md5
openssl rsa -noout -modulus -in data-api-wildcard.key | openssl md5
```
22 changes: 11 additions & 11 deletions microservices/gatewayApi/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM golang:1.15.2 AS build
WORKDIR /deck
RUN git clone https://github.com/kong/deck.git
RUN cd deck \
&& go mod download \
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o deck \
-ldflags "-s -w -X github.com/kong/deck/cmd.VERSION=$TAG -X github.com/kong/deck/cmd.COMMIT=$COMMIT"
# FROM golang:1.15.2 AS build
# WORKDIR /deck
# RUN git clone https://github.com/kong/deck.git
# RUN cd deck \
# && go mod download \
# && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o deck \
# -ldflags "-s -w -X github.com/kong/deck/cmd.VERSION=$TAG -X github.com/kong/deck/cmd.COMMIT=$COMMIT"

FROM python:3.8-alpine

Expand All @@ -15,11 +15,11 @@ RUN apk add build-base libffi-dev openssl openssl-dev curl
RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" && \
chmod +x kubectl; mv kubectl /usr/local/bin/.

COPY --from=build /deck/deck /usr/local/bin
#COPY --from=build /deck/deck /usr/local/bin

#RUN curl -sL https://github.com/kong/deck/releases/download/v1.2.4/deck_1.2.4_linux_amd64.tar.gz -o deck.tar.gz && \
# tar -xf deck.tar.gz -C /tmp && \
# cp /tmp/deck /usr/local/bin/
RUN curl -sL https://github.com/kong/deck/releases/download/v1.5.0/deck_1.5.0_linux_amd64.tar.gz -o deck.tar.gz && \
tar -xf deck.tar.gz -C /tmp && \
cp /tmp/deck /usr/local/bin/

COPY requirements.txt requirements.txt

Expand Down
30 changes: 1 addition & 29 deletions microservices/gatewayApi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,16 @@
from flask_cors import CORS
import threading

from auth.token import OIDCDiscovery

import v1.v1 as v1

from swagger_ui import api_doc
from jinja2 import Template


def set_cors_headers_on_response(response):
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With'
response.headers['Access-Control-Allow-Methods'] = 'OPTIONS'
return response

def setup_swagger_docs (app):
conf = config.Config()
log = logging.getLogger(__name__)

try:
## Template the spec and write it to a temporary location
discovery = OIDCDiscovery(conf.data['oidcBaseUrl'])
tmpFile = "%s/v1.yaml" % conf.data['workingFolder']
f = open("v1/spec/v1.yaml", "r")
t = Template(f.read())
f = open(tmpFile, "w")
f.write(t.render(
server_url = "/v1",
tokeninfo_url = discovery["introspection_endpoint"],
authorization_url = discovery["authorization_endpoint"],
accesstoken_url = discovery["token_endpoint"]
))
api_doc(app, config_path=tmpFile, url_prefix='/api/doc', title='API doc')
log.info("Configured /api/doc")
except:
log.error("Failed to do OIDC Discovery, sleeping 5 seconds and trying again.")
time.sleep(5)
setup_swagger_docs(app)


def create_app(test_config=None):
Expand Down Expand Up @@ -78,8 +52,6 @@ def create_app(test_config=None):
v1.Register(app)
Compress(app)

t = threading.Thread(name='child procs', target=setup_swagger_docs, args=(app,))
t.start()

@app.before_request
def before_request():
Expand Down
3 changes: 2 additions & 1 deletion microservices/gatewayApi/auth/token.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import requests
import time
from authlib.jose import jwt
from authlib.jose.errors import JoseError, ExpiredTokenError
from authlib.oauth2.rfc6749 import TokenMixin, time
from authlib.oauth2.rfc6749 import TokenMixin
from authlib.oauth2.rfc6750 import BearerTokenValidator
from flask import current_app, g
from config import Config
Expand Down
32 changes: 30 additions & 2 deletions microservices/gatewayApi/clients/ocp_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ def prepare_delete_routes (ns, select_tag, rootPath):

return len(delete_list)

def prepare_route_last_version (ns, select_tag):
log = app.logger

args = [
"kubectl", "get", "routes", "-l", "aps-select-tag=%s" % select_tag, "-o", "json"
]
run = Popen(args, stdout=PIPE, stderr=PIPE)
out, err = run.communicate()
if run.returncode != 0:
log.error("Failed to get existing routes", out, err)
raise Exception("Failed to get existing routes")

resource_versions = {}

existing = json.loads(out)
for route in existing['items']:
resource_versions[ route['metadata']['name'] ] = route['metadata']['resourceVersion']
return resource_versions

def prepare_apply_routes (ns, select_tag, is_host_transform_enabled, rootPath):
log = app.logger
ssl_key_path = "/ssl/tls.key"
Expand All @@ -132,6 +151,7 @@ def prepare_apply_routes (ns, select_tag, is_host_transform_enabled, rootPath):
kind: Route
metadata:
name: ${name}
resourceVersion: "${resource_version}"
annotations:
haproxy.router.openshift.io/timeout: 30m
labels:
Expand Down Expand Up @@ -167,6 +187,8 @@ def prepare_apply_routes (ns, select_tag, is_host_transform_enabled, rootPath):
ts = int(time.time())
fmt_time = datetime.now().strftime("%Y.%m-%b.%d")

resource_versions = prepare_route_last_version(ns, select_tag)

with open(out_filename, 'w') as out_file:
index = 1
for host in host_list:
Expand All @@ -182,8 +204,14 @@ def prepare_apply_routes (ns, select_tag, is_host_transform_enabled, rootPath):
ssl_key = read_and_indent("/ssl/%s.key" % ssl_ref, 8)
ssl_crt = read_and_indent("/ssl/%s.crt" % ssl_ref, 8)

log.debug("[%s] Route A %03d wild-%s-%s" % (select_tag, index, select_tag.replace('.','-'), host))
out_file.write(template.substitute(name="wild-%s-%s" % (select_tag.replace('.','-'), host), ns=ns, select_tag=select_tag, host=host, path='/', ssl_ref=ssl_ref, ssl_key=ssl_key, ssl_crt=ssl_crt, serviceName='kong-kong-proxy', timestamp=ts, fmt_time=fmt_time))
name = "wild-%s-%s" % (select_tag.replace('.','-'), host)

resource_version = ""
if name in resource_versions:
resource_version = resource_versions[name]

log.debug("[%s] Route A %03d wild-%s-%s (ver.%s)" % (select_tag, index, select_tag.replace('.','-'), host, resource_version))
out_file.write(template.substitute(name=name, ns=ns, select_tag=select_tag, resource_version=resource_version, host=host, path='/', ssl_ref=ssl_ref, ssl_key=ssl_key, ssl_crt=ssl_crt, serviceName='kong-kong-proxy', timestamp=ts, fmt_time=fmt_time))
out_file.write('\n---\n')
index = index + 1

Expand Down
50 changes: 50 additions & 0 deletions microservices/gatewayApi/clients/portal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from flask import current_app as app
import sys
import requests
import traceback
import urllib.parse

#
# 'type', 'name', 'action', 'message', 'refId', 'namespace'


def record_namespace_event (uuid, action, result, namespace, message = ""):
record_activity ({
'id': uuid,
'type': 'GatewayNamespace',
'action': action,
'result': result,
'name': 'N/A',
'message': message,
'refId': '',
'namespace': namespace
})

def record_gateway_event (uuid, action, result, namespace, message = ""):
record_activity ({
'id': uuid,
'type': 'GatewayConfig',
'action': action,
'result': result,
'name': 'N/A',
'message': message,
'refId': '',
'namespace': namespace
})

def record_activity (activity):
log = app.logger
portal_url = app.config['portal']['url']

log.debug("record_activity %s : %s %s" % (portal_url, activity['id'], activity['result']))

if portal_url != "":
headers = {
"Content-Type": "application/json"
}
try:
r = requests.put("%s/feed/Activity" % portal_url, headers=headers, json=activity, timeout=5)
log.info("Request Record Activity %s : %d" % (portal_url, r.status_code))
except Exception as ex:
log.error("Error recording activity %s : %s" % (portal_url, str(ex)))
traceback.print_exc(file=sys.stdout)
6 changes: 6 additions & 0 deletions microservices/gatewayApi/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ cat > "${CONFIG_PATH:-./config/default.json}" <<EOF
"username": "$KC_USERNAME",
"password": "$KC_PASSWORD"
},
"applyAporetoNSP": ${NSP_ENABLED:-true},
"protectedKubeNamespaces": "${PROTECTED_KUBE_NAMESPACES:-[]}",
"hostTransformation": {
"enabled": ${HOST_TRANSFORM_ENABLED:-false},
"baseUrl": "${HOST_TRANSFORM_BASE_URL}"
},
"portal": {
"url": "${PORTAL_ACTIVITY_URL:-""}",
"token": "${PORTAL_ACTIVITY_TOKEN}"
},
"plugins": {
"rate_limiting": {
"redis_database": 0,
Expand Down
2 changes: 1 addition & 1 deletion microservices/gatewayApi/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ply==3.10
cryptography==3.1.1
authlib==0.14.3
authlib==0.15.3
swagger-ui-py==0.3.0
Jinja2==2.11.2
PyYAML==5.3.1
Expand Down
1 change: 1 addition & 0 deletions microservices/gatewayApi/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def run_tests(self):
'pyyaml',
'mongoengine',
'pyhcl',
'python-keycloak',
'requests',
'swagger-ui-py==0.3.0',
'flask-jwt-simple',
Expand Down
Loading

0 comments on commit ad79320

Please sign in to comment.