Skip to content

Commit

Permalink
Pam apigee setup (#218)
Browse files Browse the repository at this point in the history
* set up apigee proxy for pam
  • Loading branch information
bolyachevets authored Feb 13, 2025
1 parent 0e84e2e commit 966b36a
Show file tree
Hide file tree
Showing 14 changed files with 479 additions and 91 deletions.
102 changes: 102 additions & 0 deletions gcp/pam/cloud-functions/apigee-pam-api-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

# Instructions for Deploying API Proxy and Token Signing

### Run the following to zip the API proxy:

```bash
zip -r apiProxy.zip apiProxy
```

Then, import the archive via the Apigee menu in the GCP console.

**Note:** You can run the Apigee linting tool to debug:

```bash
apigeelint -s ./apigee-pam-api-proxy -f table.js --profile apigeex
```

(See installation instructions [here](https://github.com/apigee/apigeelint))

# How to set up authentication for apigee

### References:
- [Create short-lived credentials](https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#rest_5)
- [Apigee to Cloud Function Auth](https://www.googlecloudcommunity.com/gc/Apigee/Apigee-To-Cloud-function-Auth/m-p/700286)
- [Apigee Samples - Cloud Functions](https://github.com/GoogleCloudPlatform/apigee-samples/tree/main/cloud-functions)
- [Manual Steps for Cloud Functions](https://github.com/GoogleCloudPlatform/apigee-samples/blob/main/cloud-functions/Manual-Steps.md)

### Test Signing Tokens via `gcloud`

1. **Permissions:**

```bash
gcloud projects add-iam-policy-binding okagqp-prod --member="user:[email protected]" --role="roles/iam.serviceAccountTokenCreator"
```

**Payload (payload.json):**

```json
{
"audience": "<CLOUD_FUNCTION_URL>",
"includeEmail": "true"
}
```

**Sign the JWT:**

```bash
gcloud iam service-accounts sign-jwt --iam-account="[email protected]" payload.json signed_jwt.json
```

2. **Generate Token via Curl:**

```bash
curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json; charset=utf-8" --data "@payload.json" "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateIdToken"
```

Now, save the token in a variable:

```bash
SA_ID_TOKEN=$(curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json; charset=utf-8" --data "@payload.json" "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateIdToken" | jq -r '.token')
```

3. **Call the Cloud Function URL with the Token:**

Make sure `[email protected]` has the `Cloud Functions Invoker` role.

```bash
gcloud functions add-invoker-policy-binding "pam-request-grant-create" --member="serviceAccount:[email protected]" --region="northamerica-northeast1" --project="<TARGET_PROJECT_ID>"
```

Now, call the Cloud Function:

```bash
curl -i -H "Authorization: Bearer $SA_ID_TOKEN" "<CLOUD_FUNCTION_URL>" \
-H "Content-Type: application/json" \
-d '{
"assignee": "...",
"entitlement": "...",
"duration": 5,
"robot": ...,
"permissions": "...",
"database": "..."
}'
```

### Test Apigee Endpoint

Test the Apigee proxy endpoint:

```bash
curl -X POST "<APIGEE_PROXY_URL>"
-H "Content-Type: application/json" \
-H "X-API-Key: ..." \
-d '{
"assignee": "...",
"entitlement": "...",
"duration": 5,
"robot": ...,
"permissions": "...",
"database": "..."
}'
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<APIProxy name="pam-create-proxy">
<Description>Proxy for database PAM routing</Description>
</APIProxy>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Javascript name="ExtractDatabase">
<Source>
var payload = JSON.parse(request.content);
var database = payload.database;
context.setVariable("target.database", database);
</Source>
</Javascript>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<AssignMessage name="RemoveAPIKey">
<Remove>
<Headers>
<Header name="X-API-Key"/>
</Headers>
</Remove>
</AssignMessage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Javascript name="RouteToBackend">
<Source>
var database = context.getVariable("target.database");

if (!database) {
throw new Error("Database variable is not set");
}

database = database.trim();

var apiUrls = {
"warehouse": "https://northamerica-northeast1-mvnjri-prod.cloudfunctions.net/pam-request-grant-create",
"notify": "https://northamerica-northeast1-c4hnrd-prod.cloudfunctions.net/pam-request-grant-create-notify",
"docs": "https://northamerica-northeast1-c4hnrd-prod.cloudfunctions.net/pam-request-grant-create-docs",
"auth": "https://northamerica-northeast1-gtksf3-prod.cloudfunctions.net/pam-request-grant-create-auth-db",
"bor": "https://northamerica-northeast1-yfjq17-prod.cloudfunctions.net/pam-request-grant-create-bor",
"business-ar": "https://northamerica-northeast1-a083gt-prod.cloudfunctions.net/pam-request-grant-create-business-ar",
"legal-entities": "https://northamerica-northeast1-a083gt-prod.cloudfunctions.net/pam-request-grant-create-legal-entities",
"bni-hub": "https://northamerica-northeast1-keee67-prod.cloudfunctions.net/pam-request-grant-create-bni-hub",
"vans-prod": "https://northamerica-northeast1-keee67-prod.cloudfunctions.net/pam-request-grant-create-vans-db-prod",
"ppr": "https://northamerica-northeast1-eogruh-prod.cloudfunctions.net/pam-request-grant-create-ppr",
"search": "https://northamerica-northeast1-k973yf-prod.cloudfunctions.net/pam-request-grant-create-search"
};

if (apiUrls[database]) {
context.setVariable("pam.api.url", apiUrls[database]);
} else {
throw new Error("Invalid database: " + database);
}

</Source>
</Javascript>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<VerifyAPIKey name="VerifyAPIKey">
<DisplayName>VerifyAPIKey</DisplayName>
<Properties/>
<APIKey ref="request.header.X-API-Key"/>
</VerifyAPIKey>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<ProxyEndpoint name="default">
<HTTPProxyConnection>
<BasePath>/registries/pam/create</BasePath> <!-- Define the API base path -->
<VirtualHosts>
<VirtualHost>default</VirtualHost> <!-- Reference the default virtual host -->
</VirtualHosts>
</HTTPProxyConnection>

<PreFlow>
<Request>
<Step>
<Name>VerifyAPIKey</Name>
</Step>
<Step>
<Name>RemoveAPIKey</Name>
</Step>
<Step>
<Name>ExtractDatabase</Name> <!-- Extract 'database' from the payload -->
</Step>
<Step>
<Name>RouteToBackend</Name> <!-- Set the target URL based on 'database' -->
</Step>
</Request>
<Response/>
</PreFlow>

<RouteRule name="default">
<TargetEndpoint>target-default</TargetEndpoint>
</RouteRule>
</ProxyEndpoint>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<TargetEndpoint name="target-default">
<HTTPTargetConnection>
<Authentication>
<GoogleIDToken>
<Audience useTargetUrl='true'></Audience>
<IncludeEmail>true</IncludeEmail>
</GoogleIDToken>
</Authentication>
<SSLInfo>
<Enabled>true</Enabled>
<Enforce>true</Enforce>
</SSLInfo>
<URL>{pam.api.url}</URL>
</HTTPTargetConnection>
</TargetEndpoint>
3 changes: 2 additions & 1 deletion gcp/pam/cloud-functions/pam-request-grant-create/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
instance_connection_name = os.environ['DB_INSTANCE_CONNECTION_NAME']
username = os.environ['DB_USER']
dbname = os.environ['DB_NAME']
pubsub_topic = os.environ['PUBSUB_TOPIC']
project_number = os.environ['PROJECT_NUMBER']
project_id = os.environ['PROJECT_ID']
secret_id = os.environ['SECRET_ID']
Expand Down Expand Up @@ -237,7 +238,7 @@ def create_pam_grant_request(request):
if robot:
update_project_iam_policy_with_condition(project_id, entitlement, assignee, duration)
create_iam_user(project_number, instance_connection_name, assignee)
create_one_time_scheduler_job(project_id, 'pam-revoke-topic', entitlement, assignee, duration, robot)
create_one_time_scheduler_job(project_id, pubsub_topic, entitlement, assignee, duration, robot)

global db
if not db:
Expand Down
24 changes: 24 additions & 0 deletions gcp/pam/db-roles/readonly.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
DO $$
DECLARE
schema_name text;
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'readonly') THEN
CREATE ROLE readonly;
END IF;
-- Loop through all schemas except system schemas
FOR schema_name IN
SELECT s.schema_name
FROM information_schema.schemata s
WHERE s.schema_name NOT IN ('pg_catalog', 'information_schema') -- Exclude system schemas
LOOP
-- Grant USAGE on the schema
EXECUTE format('GRANT USAGE ON SCHEMA %I TO readonly;', schema_name);

-- Grant SELECT on all existing tables in the schema
EXECUTE format('GRANT SELECT ON ALL TABLES IN SCHEMA %I TO readonly;', schema_name);

-- Set default privileges for future tables
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT SELECT ON TABLES TO readonly;', schema_name);
END LOOP;
END;
$$;
54 changes: 54 additions & 0 deletions gcp/pam/db-roles/readwrite.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
DO $$
DECLARE
schema_name text;
sequence_name text;
type_name text;
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'readwrite') THEN
CREATE ROLE readwrite;
END IF;
-- Loop through all schemas except system schemas
FOR schema_name IN
SELECT s.schema_name
FROM information_schema.schemata s
WHERE s.schema_name NOT IN ('pg_catalog', 'information_schema')
LOOP
BEGIN
-- Grant USAGE on the schema
EXECUTE format('GRANT USAGE ON SCHEMA %I TO readwrite;', schema_name);

-- Grant privileges on all existing tables
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO readwrite;', schema_name);

-- Set default privileges for future tables
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite;', schema_name);

-- Grant privileges on all existing sequences
EXECUTE format('GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA %I TO readwrite;', schema_name);

-- Set default privileges for future sequences
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT USAGE, SELECT ON SEQUENCES TO readwrite;', schema_name);

-- Grant EXECUTE on all existing functions
EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA %I TO readwrite;', schema_name);

-- Set default privileges for future functions
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT EXECUTE ON FUNCTIONS TO readwrite;', schema_name);

-- Grant USAGE on all user-defined types (composite and enum types)
FOR type_name IN
SELECT t.typname
FROM pg_type t
JOIN pg_namespace n ON t.typnamespace = n.oid
WHERE n.nspname = schema_name AND t.typcategory IN ('C', 'E')
LOOP
EXECUTE format('GRANT USAGE ON TYPE %I.%I TO readwrite;', schema_name, type_name);
END LOOP;

-- Note: Default privileges for future types are not supported in PostgreSQL
EXCEPTION
WHEN others THEN
RAISE WARNING 'Failed to apply privileges in schema %: %', schema_name, SQLERRM;
END;
END LOOP;
END $$;
1 change: 0 additions & 1 deletion gcp/pam/entitlements/itops-lists.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
emailLists:
requesters:
- [email protected]
- [email protected]
approvers:
- [email protected]
4 changes: 3 additions & 1 deletion gcp/pam/generate-entitlements.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash

declare -a projects=("mvnjri")
# declare -a projects=("mvnjri")

projects=("$@")

declare -a environments=("prod")

Expand Down
Loading

0 comments on commit 966b36a

Please sign in to comment.