Skip to content

Commit 9fd4685

Browse files
direct PAT (#58)
1 parent 5f6123b commit 9fd4685

File tree

4 files changed

+40
-325
lines changed

4 files changed

+40
-325
lines changed

ingress-cors/README.md

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Introduction
2-
After completing the [common setup](https://docs.snowflake.com/en/developer-guide/snowpark-container-services/tutorials/common-setup), you are ready to create a service. In this tutorial, you will create one service (named echo_service_with_cors) that exposes an endpoint to respond to CORS requests. Then you will create a service locally to send CORS requests to the endpoint:
2+
After completing the [common setup](https://docs.snowflake.com/en/developer-guide/snowpark-container-services/tutorials/common-setup), you are ready to create a service. In this tutorial, you will create one service (named echo_service_with_cors) that exposes an endpoint to respond to CORS requests. Then you will create a service locally to send CORS requests to the endpoint using **PAT token authentication**:
33

44
**1. /healthcheck:** this method should respond with "I'm ready!"
55

@@ -15,9 +15,9 @@ At a high-level, this tutorial contains the following steps:
1515

1616
3. Create the service by providing the service specification file and the compute pool in which to run the service.
1717

18-
6. Configure key pair authentication and/or PAT token authentication
18+
4. Configure PAT token authentication
1919

20-
7. Send CORS requests from localhost
20+
5. Send CORS requests from localhost using PAT tokens
2121

2222
<br>
2323

@@ -131,6 +131,7 @@ CREATE SERVICE echo_service_with_cors
131131
- http://localhost:8888
132132
Access-Control-Allow-Methods:
133133
- POST
134+
- GET
134135
Access-Control-Allow-Headers:
135136
- Authorization
136137
- Content-Type
@@ -169,58 +170,39 @@ Verify that you can access the endpoint by hitting the endpoint in the browser:
169170
https://ftwoqas5-myorg-myacct.snowflakecomputing.app/healthcheck
170171
```
171172

172-
# 4: Configure key pair authentication
173-
1. Configure key pair authentication for the user by following the [key pair authentication guide](https://docs.snowflake.com/en/user-guide/key-pair-auth)
173+
# 4: Configure PAT token authentication
174+
Configure a PAT token for the user by following the [PAT token guide](https://docs.snowflake.com/LIMITEDACCESS/programmatic-access-tokens)
174175

175-
2. Configure a PAT token for the user by following the [PAT token guide](https://docs.snowflake.com/LIMITEDACCESS/programmatic-access-tokens)
176+
# 5: Run the service locally
176177

177-
178-
# 5: Spin up the service locally
179-
In the cors_app folder, using a Python virtual environment, execute:
178+
## Option 1: Using Docker (Recommended)
179+
In the cors_app folder, execute:
180180
```
181-
python3 -m venv .
182-
source bin/activate
183-
pip3 install --upgrade pip
184-
pip3 install -r requirements.txt
185-
python3 echo_service.py
181+
cd cors_app
182+
docker build -t cors-app .
183+
docker run -p 8888:8888 cors-app
186184
```
187185

188186
Access the endpoint at http://localhost:8888/ui in a web browser. This causes the service to execute the ui() function (see echo_service.py).
189187

190-
<img src="cors_app_ui.png" alt="drawing" width="1200"/>
191-
192-
# 6: Send CORS requests to the endpoint on local browser
193-
194-
There are 4 functionalities the endpoint provides:
195-
196-
a. Making a GET request with info necessary for programmatic access using key pair authentication
188+
# 6: Send CORS requests to the endpoint
197189

198-
b. Making a GET request with info necessary for programmatic access using a PAT token
190+
The simplified interface now provides **2 main functionalities** using PAT token authentication:
199191

200-
c. Making a POST request with info necessary for programmatic access using key pair authentication
192+
**a. Making a GET request** - Test CORS GET requests with PAT token authentication
193+
194+
**b. Making a POST request** - Test CORS POST requests with PAT token authentication
201195

202-
d. Making a POST request with info necessary for programmatic access using a PAT token
196+
Simply input the endpoint URL and your PAT token. The interface will:
197+
- Use Bearer authentication with your PAT token
198+
- Send custom headers to test CORS configuration
199+
- Validate Access-Control-Allow-Headers and Access-Control-Expose-Headers
203200

204-
Input the endpoint, user, role, and account URL. The JWT token retrival part (a and c) / PAT token exchange (b and d) will be done automatically. For more information on how to find the account URL, visit [Account identifiers](https://docs.snowflake.com/en/user-guide/admin-account-identifier). The value of Role is automatically capitalized as Snowflake roles are always in uppercase.
205-
Note that comparing to the [programmatic access tutorial](https://docs.snowflake.com/en/developer-guide/snowpark-container-services/tutorials/tutorial-1#setup), the account and endpoint are both omitted as input, since they can be deduced from the Snowflake Account URL and the GET / POST URL respectively.
206-
Also note that all 4 functionalities validate Access-Control-Allow-Headers and Access-Control-Expose-Headers as an example.
201+
The simplified UI eliminates the complexity of key pair authentication and JWT token exchange, making it easier to test CORS functionality with just PAT tokens.
207202

208-
For a:
209-
210-
<img src="cors_get_with_key_pair.png" alt="drawing" width="600"/>
211-
212-
<br>
213-
For b:
214-
215-
<img src="cors_get_with_pat.png" alt="drawing" width="600"/>
216-
217-
<br>
218-
For c:
219-
220-
<img src="cors_post_with_key_pair.png" alt="drawing" width="600"/>
221-
222-
<br>
223-
For d:
203+
**Testing endpoints:**
204+
- GET: Test against `/healthcheck` or other GET endpoints
205+
- POST: Test against `/echo` endpoint with custom payloads
224206

225-
<img src="cors_post_with_pat.png" alt="drawing" width="600"/>
207+
Both functionalities validate CORS headers as an example of proper CORS implementation.
226208

ingress-cors/cors_app/Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
ARG BASE_IMAGE=python:3.10-slim-buster
22
FROM $BASE_IMAGE
33
COPY echo_service.py ./
4-
COPY generateJWT.py ./
54
COPY templates/ ./templates/
65
RUN pip install --upgrade pip && \
7-
pip install flask cryptography PyJWT requests
6+
pip install flask requests
87
CMD ["python3", "echo_service.py"]
98

ingress-cors/cors_app/echo_service.py

Lines changed: 3 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
from generateJWT import JWTGenerator
2-
31
from flask import Flask
42
from flask import request
53
from flask import jsonify
64
from flask import make_response
75
from flask import render_template
8-
from cryptography.hazmat.primitives.serialization import load_pem_private_key
9-
from cryptography.hazmat.backends import default_backend
106
from datetime import timedelta, timezone, datetime
117
import logging
128
import requests
@@ -16,6 +12,8 @@
1612
SERVICE_HOST = os.getenv('SERVER_HOST', '0.0.0.0')
1713
SERVICE_PORT = os.getenv('SERVER_PORT', 8888)
1814
CHARACTER_NAME = os.getenv('CHARACTER_NAME', 'I')
15+
SNOWFLAKE_HOST = os.getenv('SNOWFLAKE_HOST')
16+
SNOWFLAKE_ACCOUNT = os.getenv('SNOWFLAKE_ACCOUNT')
1917

2018

2119
def get_logger(logger_name):
@@ -48,7 +46,7 @@ def readiness_probe():
4846
@app.get("/get")
4947
def get_func():
5048
response = make_response("GET success!")
51-
response.headers['Access-Control-Allow-Origin'] = 'https://b3efa44-sfengineering-prod2-snowservices-test2.snowflakecomputing.app'
49+
response.headers['Access-Control-Allow-Origin'] = 'https://localhost:9999'
5250
response.headers['Access-Control-Allow-Credentials'] = True
5351
response.headers['Access-Control-Allow-Headers'] = 'Fake-Header-X,Fake-Header-Y,Fake-Header-Z'
5452

@@ -88,74 +86,6 @@ def ui():
8886

8987
return render_template("basic_ui.html")
9088

91-
def get_p8():
92-
p8 = ""
93-
with open("./rsa_key.p8", 'rb') as pem_in:
94-
pemlines = pem_in.read()
95-
try:
96-
# Try to access the private key without a passphrase.
97-
p8 = load_pem_private_key(pemlines, None, default_backend())
98-
logger.debug(f"p8: {p8}")
99-
except TypeError:
100-
logger.error("Failed getting private key. ")
101-
return p8
102-
103-
def token_exchange(token, role, endpoint, snowflake_account_url, isPat):
104-
scope_role = f'session:role:{role}'
105-
scope = f'{scope_role} {endpoint}'
106-
107-
if isPat:
108-
data = {
109-
'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
110-
'scope': scope,
111-
'subject_token': token,
112-
'subject_token_type': 'programmatic_access_token'
113-
}
114-
else:
115-
data = {
116-
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
117-
'scope': scope,
118-
'assertion': token,
119-
}
120-
121-
logger.info(data)
122-
response = _do_token_exchange(data, snowflake_account_url)
123-
return response.text
124-
125-
def _do_token_exchange(data, snowflake_account_url) -> requests.Response:
126-
url = f'{snowflake_account_url}/oauth/token'
127-
response = requests.post(url, data=data, verify=False)
128-
logger.info("snowflake jwt response code : %s" % response.status_code)
129-
assert 200 == response.status_code, "unable to get snowflake token"
130-
return response
131-
132-
133-
@app.route('/jwt', methods=['POST'])
134-
def handle_data():
135-
if request.method == 'POST':
136-
data = request.json
137-
logger.debug(f'data when calling /jwt: {data}')
138-
# Load the private key from the specified file.
139-
user = data.get("user")
140-
role = data.get("role")
141-
isPat = data.get("isPat")
142-
snowflake_account_url = data.get("snowflake_account_url")
143-
snowflake_account_hostname = snowflake_account_url[8:]
144-
account = snowflake_account_hostname.split('.')[0]
145-
logger.debug(f'Account from Snowflake Account URL: {account}')
146-
endpoint = data.get("endpoint")
147-
key = data.get("key")
148-
if isPat:
149-
snowflake_jwt = token_exchange(key, role=role, endpoint=endpoint,
150-
snowflake_account_url=snowflake_account_url, isPat=isPat)
151-
else:
152-
token = JWTGenerator(account, user, key, timedelta(minutes=59),
153-
timedelta(minutes=54)).get_token()
154-
snowflake_jwt = token_exchange(token, role=role, endpoint=endpoint,
155-
snowflake_account_url=snowflake_account_url, isPat=isPat)
156-
157-
return snowflake_jwt
158-
15989
@app.after_request
16090
def apply_csp(response):
16191
csp = (

0 commit comments

Comments
 (0)