Skip to content

Commit 3088908

Browse files
authored
Merge pull request #303 from oracle-devrel/vch-api-fn-authorizer
added reusable assets for authorizer functions.
2 parents 246cbbd + 9bef123 commit 3088908

File tree

17 files changed

+549
-8
lines changed

17 files changed

+549
-8
lines changed

app-dev/app-integration-and-automation/oci-api-management/README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,40 @@
22

33
Oracle Cloud Infrastructure (OCI) provides a comprehensive set of services to manage the lifecycle of APIs.
44

5-
Developers can prototype APIs easily using the integrated **Code Editor** to create API descriptions in OpenAPI format which is supported by OCI API Gateway. Code Editor comes with Git integration and built-in integration with OCI services.
5+
**Oracle API Gateway** is a highly available virtual network appliance that can receive API calls at scale and route them to back-end services, such as Serverless Functions, services running on Oracle Kubernetes Engine, Oracle Integration, ORDS and more.
66

7-
**OCI API Management** is a fully Oracle-managed, regional service that is used to provide a policy enforcement layer for HTTP traffic. The API Gateway service enables you to publish protected APIs for HTTP/s services such as Serverless Functions, services running on Oracle Kubernetes Engine, Oracle Integration, ORDS, and other services on Oracle Cloud Infrastructure and beyond.
7+
Developers can choose from a wide range of tools to create API descriptions in OpenAPI format which is supported by OCI API Gateway.
8+
9+
API Gateway provides policy enforcement such as limit the number of requests sent to back-end services, enable CORS, provide authentication and authorization, add mTLS support, modify incoming requests and outgoing responses, cache responses to improve performance and reduce load on back-end services.
810

911
API managers can create Usage Plans within API Gateway and define API access tiers. API teams can monitor the traffic and analytics of their APIs based on the usage plan and subscriptions. This enables customers to analyze usage patterns as well as unlock new revenue streams by monetizing APIs.
1012

1113
## Useful Links
1214

13-
- [OCI API Management Oracle.com Page](https://www.oracle.com/cloud/cloud-native/api-management/)
14-
- [OCI API Management Documentation](https://docs.oracle.com/iaas/Content/APIGateway/home.htm)
15-
- [Price List](https://www.oracle.com/cloud/price-list/#api)
1615
- [Quick Start Guide](https://docs.oracle.com/en-us/iaas/Content/APIGateway/Tasks/apigatewayquickstartsetupcreatedeploy.htm)
1716
- YouTube Videos
1817
- [API Gateway Overview by Product Management](https://youtu.be/10U6kTh_0Lc)
1918
- [Hands on lab - how to create an API Gateway](https://youtu.be/hES55nIQH0Y)
20-
- [Blogs](https://blogs.oracle.com/author/robert-wunderlich) by PM Robert Wunderlich
19+
- [Articles](https://blogs.oracle.com/author/robert-wunderlich) by Product Management
2120
- [Terraform examples](https://github.com/oracle/terraform-provider-oci/tree/master/examples/api_gateway)
2221
- Oracle Functions Samples – API Gateway Authorizer Functions
2322
- https://github.com/oracle-samples/oracle-functions-samples → See section Functions and API Gateway
2423
- https://github.com/oracle-samples/sample.fusion-ords-identityprop
25-
- [Oracle Architecture Center](https://docs.oracle.com/solutions/?q=&cType=reference-architectures&product=API%20Gateway&sort=date-desc&lang=en) - Reference Architectures with API Gateway
24+
- [Oracle Architecture Center](https://docs.oracle.com/solutions/?q=&cType=reference-architectures&product=API%20Gateway&sort=date-desc&lang=en)
25+
- Reference Architectures with API Gateway
26+
27+
### General Product Links
28+
29+
- [API Management Product Page](https://www.oracle.com/cloud/cloud-native/api-management/)
30+
- [API Gateway Documentation](https://docs.oracle.com/iaas/Content/APIGateway/home.htm)
2631

2732
## Reusable Assets Overview
2833

29-
Relevant reusable assets can be found in subfolders.
34+
Reusable assets can be found in subfolders:
35+
- [fn-authorizer-apigw-oic](fn-authorizer-apigw-oic)
36+
- Example authorizer function for API Gateway with OIC backend.
37+
- [fn-authorizer-apigw-ords](fn-authorizer-apigw-ords)
38+
- Example authorizer function for API Gateway with ORDS backend.
3039

3140
# License
3241

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Copyright (c) 2023 Oracle and/or its affiliates.
2+
3+
The Universal Permissive License (UPL), Version 1.0
4+
5+
Subject to the condition set forth below, permission is hereby granted to any
6+
person obtaining a copy of this software, associated documentation and/or data
7+
(collectively the "Software"), free of charge and under any and all copyright
8+
rights in the Software, and any and all patent rights owned or freely
9+
licensable by each licensor hereunder covering either (i) the unmodified
10+
Software as contributed to or provided by such licensor, or (ii) the Larger
11+
Works (as defined below), to deal in both
12+
13+
(a) the Software, and
14+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
15+
one is included with the Software (each a "Larger Work" to which the Software
16+
is contributed by such licensors),
17+
18+
without restriction, including without limitation the rights to copy, create
19+
derivative works of, display, perform, and distribute the Software and make,
20+
use, sell, offer for sale, import, export, have made, and have sold the
21+
Software and the Larger Work(s), and to sublicense the foregoing rights on
22+
either these or other terms.
23+
24+
This license is subject to the following condition:
25+
The above copyright notice and either this complete permission notice or at
26+
a minimum a reference to the UPL must be included in all copies or
27+
substantial portions of the Software.
28+
29+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35+
SOFTWARE.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# fn-authorizer-apigw-oic
2+
3+
*Example authorizer function for API Gateway with OIC backend.*
4+
5+
# When to use this asset?
6+
7+
Use this asset as an example, when you want to decouple the client authentication in API Gateway from the OIC native mechanism, by using a different Identity Provider (IdP) or a different authentication mechanism. This example is using IDCS as Identity Provider for the client authentication. You can adapt to use any OAuth compliant IdP.
8+
9+
# How to use this asset?
10+
11+
This asset is provided as an example. Please tailor the code according to your needs and your context.
12+
13+
See [oci-apigw-oic-auth/README.md](oci-apigw-oic-auth/README.md) for asset details.
14+
15+
# License
16+
17+
Copyright (c) 2023, Oracle and/or its affiliates.
18+
19+
Licensed under the Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# FN App Configuration
2+
* idcs_introspection_endpoint https://<idcs_host>/oauth2/v1/introspect
3+
* introspection_idcs_app_client_id
4+
* introspection_idcs_app_client_secret_ocid
5+
* idcs_token_endpoint https://<idcs_host>/oauth2/v1/token
6+
* oic_idcs_app_client_id
7+
* oic_idcs_app_client_secret_ocid
8+
* oic_scope
9+
10+
![configuration](images/fn-app-configuration.png)
11+
12+
# Deploy and test the function
13+
14+
cd oci-apigw-oic-auth
15+
fn -v deploy --app <my-fn-app>
16+
17+
echo -n '{"data": {"token": "Bearer <token-value>"}}' | fn invoke <my-fn-app> oci-apigw-oic-auth | jq .
18+
19+
# Inspiration and Credits
20+
* [OIC & OAuth 2.0 - Part 3](http://niallcblogs.blogspot.com/2022/04/908-oic-oauth-20-part-3.html)
21+
* [Protect OIC REST APIs with OCI API Gateway and OAuth2 – 2/2](https://mytechretreat.com/protect-oic-rest-apis-with-oci-api-gateway-and-oauth2-2-2/)
22+
* [Authenticating Oracle Integration flows using OAuth token from 3rd party provider](https://blogs.oracle.com/integration/post/authenticating-oic-flows-through-third-party-bearer-token)
23+
24+
# Licence
25+
26+
Copyright (c) 2023, Oracle and/or its affiliates.
27+
28+
Licensed under the Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Copyright (c) 2023 Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
import datetime
4+
import io
5+
import json
6+
import logging
7+
from datetime import timedelta
8+
9+
import requests
10+
from fdk import response
11+
from requests.auth import HTTPBasicAuth
12+
13+
import ociVault
14+
15+
oauth_apps = {}
16+
17+
def initContext(context):
18+
# This method takes elements from the Application Context and from OCI Vault to create the OAuth App Clients object.
19+
if (len(oauth_apps) < 2):
20+
logging.getLogger().info('Retriving details about the API and backend OAuth Apps')
21+
try:
22+
logging.getLogger().info('initContext: Initializing context')
23+
24+
# Using ociVault
25+
oauth_apps['apigw'] = {'introspection_endpoint': context['idcs_introspection_endpoint'],
26+
'client_id': context['introspection_idcs_app_client_id'],
27+
'client_secret': ociVault.getSecret(context['introspection_idcs_app_client_secret_ocid'])}
28+
oauth_apps['oic'] = {'token_endpoint': context['idcs_token_endpoint'],
29+
'client_id': context['oic_idcs_app_client_id'],
30+
'client_secret': ociVault.getSecret(context['oic_idcs_app_client_secret_ocid']), 'scope': context['oic_scope']}
31+
32+
except Exception as ex:
33+
logging.getLogger().error('initContext: Failed to get config or secrets')
34+
print("ERROR [initContext]: Failed to get the configs", ex, flush=True)
35+
raise
36+
else:
37+
logging.getLogger().info('initContext: OAuth Apps already stored')
38+
39+
def introspectToken(access_token, introspection_endpoint, client_id, client_secret):
40+
# This method handles the introspection of the received auth token to IDCS.
41+
payload = {'token': access_token}
42+
headers = {'Content-Type' : 'application/x-www-form-urlencoded;charset=UTF-8',
43+
'Accept': 'application/json'}
44+
45+
try:
46+
token = requests.post(introspection_endpoint,
47+
data=payload,
48+
headers=headers,
49+
auth=HTTPBasicAuth(client_id, client_secret))
50+
51+
except Exception as ex:
52+
logging.getLogger().error("introspectToken: Failed to introspect token" + ex)
53+
raise
54+
55+
return token.json()
56+
57+
def getBackEndAuthToken(token_endpoint, client_id, client_secret, scope):
58+
# This method gets the token from the back-end system (oic in this case)
59+
payload = {'grant_type': 'client_credentials', 'scope': scope}
60+
headers = {'Content-Type' : 'application/x-www-form-urlencoded;charset=UTF-8',
61+
'Accept': 'application/json'}
62+
63+
try:
64+
backend_token = requests.post(token_endpoint,
65+
data=payload,
66+
headers=headers,
67+
auth=HTTPBasicAuth(client_id, client_secret))
68+
69+
logging.getLogger().info("getBackEndAuthToken: Got the backend token " + backend_token.text)
70+
71+
except Exception as ex:
72+
logging.getLogger().error("getBackEndAuthToken: Failed to get the backend token" + ex)
73+
raise
74+
75+
return backend_token.json()
76+
77+
def getAuthContext(token, client_apps):
78+
# This method populates the Auth Context that will be returned to the gateway.
79+
auth_context = {}
80+
81+
# Calling IDCS to validate the token and retrieve the client info
82+
try:
83+
token_info = introspectToken(token[len('Bearer '):], client_apps['apigw']['introspection_endpoint'], client_apps['apigw']['client_id'], client_apps['apigw']['client_secret'])
84+
85+
except Exception as ex:
86+
logging.getLogger().error("getAuthContext: Failed to introspect token" + ex)
87+
raise
88+
89+
# If IDCS confirmed the token is valid and active, we can proceed to populate the auth context
90+
if (token_info['active'] == True):
91+
auth_context['active'] = True
92+
# auth_context['principal'] = token_info['sub']
93+
auth_context['client_id'] = token_info['client_id']
94+
auth_context['scope'] = token_info['scope']
95+
96+
# Retrieving the back-end Token
97+
backend_token = getBackEndAuthToken(client_apps['oic']['token_endpoint'], client_apps['oic']['client_id'], client_apps['oic']['client_secret'], client_apps['oic']['scope'])
98+
99+
# The maximum TTL for this auth is the lesser of the API Client Auth (IDCS) and the Gateway Client Auth (oic)
100+
if (datetime.datetime.fromtimestamp(token_info['exp']) < (datetime.datetime.utcnow() + timedelta(seconds=backend_token['expires_in']))):
101+
auth_context['expiresAt'] = (datetime.datetime.fromtimestamp(token_info['exp'])).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
102+
else:
103+
auth_context['expiresAt'] = (datetime.datetime.utcnow() + timedelta(seconds=backend_token['expires_in'])).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
104+
105+
# Storing the back_end_token in the context of the auth decision so we can map it to Authorization header using the request/response transformation policy
106+
auth_context['context'] = {'back_end_token': ('Bearer ' + str(backend_token['access_token']))}
107+
108+
else:
109+
# API Client token is not active, so we will go ahead and respond with the wwwAuthenticate header
110+
auth_context['active'] = False
111+
auth_context['wwwAuthenticate'] = 'Bearer realm=\"identity.oraclecloud.com\"'
112+
113+
return(auth_context)
114+
115+
def handler(ctx, data: io.BytesIO=None):
116+
logging.getLogger().info('Entered Handler')
117+
initContext(dict(ctx.Config()))
118+
119+
auth_context = {}
120+
try:
121+
gateway_auth = json.loads(data.getvalue())
122+
123+
auth_context = getAuthContext(gateway_auth['data']['token'], oauth_apps)
124+
125+
if (auth_context['active']):
126+
logging.getLogger().info('Authorizer returning 200...')
127+
return response.Response(
128+
ctx,
129+
response_data=json.dumps(auth_context),
130+
status_code = 200,
131+
headers={"Content-Type": "application/json"}
132+
)
133+
else:
134+
logging.getLogger().info('Authorizer returning 401...')
135+
return response.Response(
136+
ctx,
137+
response_data=json.dumps(str(auth_context)),
138+
status_code = 401,
139+
headers={"Content-Type": "application/json"}
140+
)
141+
142+
except (Exception, ValueError) as ex:
143+
logging.getLogger().info('error parsing json payload: ' + str(ex))
144+
145+
return response.Response(
146+
ctx,
147+
response_data=json.dumps(str(auth_context)),
148+
status_code = 401,
149+
headers={"Content-Type": "application/json"}
150+
)
151+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2023 Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
schema_version: 20180708
4+
name: oci-apigw-oic-auth
5+
version: 0.0.1
6+
runtime: python
7+
entrypoint: /python/bin/fdk /function/func.py handler
8+
memory: 256
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright (c) 2023 Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
#
4+
# Utility Function to get secrets from OCI Vault
5+
import logging
6+
import oci
7+
import base64
8+
9+
def getSecret(ocid):
10+
signer = oci.auth.signers.get_resource_principals_signer()
11+
try:
12+
client = oci.secrets.SecretsClient({}, signer=signer)
13+
secret_content = client.get_secret_bundle(ocid).data.secret_bundle_content.content.encode('utf-8')
14+
decrypted_secret_content = base64.b64decode(secret_content).decode('utf-8')
15+
except Exception as ex:
16+
logging.getLogger().error("getSecret: Failed to get Secret" + ex)
17+
print("Error [getSecret]: failed to retrieve", ex, flush=True)
18+
raise
19+
return decrypted_secret_content
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright (c) 2023 Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
fdk
4+
oci
5+
requests
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Copyright (c) 2023 Oracle and/or its affiliates.
2+
3+
The Universal Permissive License (UPL), Version 1.0
4+
5+
Subject to the condition set forth below, permission is hereby granted to any
6+
person obtaining a copy of this software, associated documentation and/or data
7+
(collectively the "Software"), free of charge and under any and all copyright
8+
rights in the Software, and any and all patent rights owned or freely
9+
licensable by each licensor hereunder covering either (i) the unmodified
10+
Software as contributed to or provided by such licensor, or (ii) the Larger
11+
Works (as defined below), to deal in both
12+
13+
(a) the Software, and
14+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
15+
one is included with the Software (each a "Larger Work" to which the Software
16+
is contributed by such licensors),
17+
18+
without restriction, including without limitation the rights to copy, create
19+
derivative works of, display, perform, and distribute the Software and make,
20+
use, sell, offer for sale, import, export, have made, and have sold the
21+
Software and the Larger Work(s), and to sublicense the foregoing rights on
22+
either these or other terms.
23+
24+
This license is subject to the following condition:
25+
The above copyright notice and either this complete permission notice or at
26+
a minimum a reference to the UPL must be included in all copies or
27+
substantial portions of the Software.
28+
29+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35+
SOFTWARE.

0 commit comments

Comments
 (0)