Skip to content

Commit

Permalink
Merge pull request #512 from splitgraph/oidc-sts-test
Browse files Browse the repository at this point in the history
Extend assume role tests
  • Loading branch information
gruuya authored Apr 2, 2024
2 parents cdaaf96 + b7eeadc commit 3ad15f7
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 38 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ assert_unordered = "0.3"
aws-config = { version = "1.0.1", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.1.5", features = ["hardcoded-credentials"] }
aws-sdk-sts = { version = "1.3.1", features = ["behavior-version-latest"] }
aws-smithy-async = "1.1.7"
rstest = "*"
serial_test = "2"
tonic-reflection = "0.10"
Expand Down
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,27 @@ services:
- "5432:5432"
restart: unless-stopped

dex:
image: ghcr.io/dexidp/dex
ports:
- "5556:5556"
volumes:
- ./tests/dex.yaml:/config-dev.yaml
command: dex serve /config-dev.yaml

minio:
image: minio/minio:latest
ports:
- 9000:9000
- 9001:9001
environment:
MINIO_CONSOLE_ADDRESS: ":9001"
DEX_HOST: "localhost"
DEX_PORT: "5556"
MINIO_IDENTITY_OPENID_CONFIG_URL: "http://dex:5556/dex/.well-known/openid-configuration"
MINIO_IDENTITY_OPENID_CLIENT_ID: "example-app"
MINIO_IDENTITY_OPENID_CLIENT_SECRET: "ZXhhbXBsZS1hcHAtc2VjcmV0"
MINIO_IDENTITY_OPENID_CLAIM_NAME: "name"
command: minio server /data

createbuckets:
Expand Down
5 changes: 4 additions & 1 deletion tests/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use assert_cmd::prelude::*; // Add methods on commands
use rstest::rstest;
use seafowl::config::schema::DEFAULT_SQLITE_DB;
use seafowl::repository::postgres::testutils::get_random_schema;
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::process::{Command, Stdio}; // Run programs
use tempfile::{Builder, TempDir};

use crate::get_sts_creds;
use crate::{get_sts_creds, AssumeRoleTarget};

mod basic;
mod one_off;
Expand Down
23 changes: 16 additions & 7 deletions tests/cli/one_off.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
use crate::cli::*;

#[rstest]
#[tokio::test]
async fn test_one_off_with_token() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = setup_temp_config_and_data_dir()?;
let (access_key_id, secret_access_key, session_token) = get_sts_creds().await;
async fn test_one_off_with_token(
#[values(AssumeRoleTarget::MinioUser, AssumeRoleTarget::DexOIDC)]
role: AssumeRoleTarget,
) -> Result<(), Box<dyn std::error::Error>> {
let (access_key_id, secret_access_key, session_token) = get_sts_creds(role).await;

let output = Command::cargo_bin("seafowl")?
.arg("--one-off")
.arg("CREATE TABLE one_off AS VALUES (1, 'one'), (2, 'two'); SELECT * FROM one_off")
.arg("-c")
.arg(temp_dir.path().join(TEST_CONFIG_FILE))
// Override the object_store section with the custom credentials
.env("SEAFOWL__OBJECT_STORE__TYPE", "s3")
.env("SEAFOWL__OBJECT_STORE__ACCESS_KEY_ID", access_key_id)
.env("SEAFOWL__OBJECT_STORE__SECRET_ACCESS_KEY", secret_access_key)
.env("SEAFOWL__OBJECT_STORE__SESSION_TOKEN", session_token)
.env("SEAFOWL__OBJECT_STORE__ENDPOINT", "http://localhost:9000")
.env("SEAFOWL__OBJECT_STORE__BUCKET", "seafowl-test-bucket")
.env("SEAFOWL__CATALOG__TYPE", "postgres")
.env("SEAFOWL__CATALOG__DSN", env::var("DATABASE_URL").unwrap())
.env("SEAFOWL__CATALOG__SCHEMA", get_random_schema())
.output()?;

assert!(output.status.success());
assert!(
output.status.success(),
"Command unsuccessful\nCaptured stdout:\n{}\n\nCaptured stderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);

// Convert the stdout bytes to a text representation
let output: Vec<_> = String::from_utf8_lossy(&output.stdout)
Expand Down
55 changes: 55 additions & 0 deletions tests/dex.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Mock Dex OIDC config, appropriated from the example config:
# https://github.com/dexidp/dex/blob/master/examples/config-dev.yaml

# The base path of dex and the external name of the OpenID Connect service.
# This is the canonical URL that all clients MUST use to refer to dex. If a
# path is provided, dex's HTTP service will listen at a non-root URL.
issuer: http://dex:5556/dex

# The storage configuration determines where dex stores its state. Supported
# options include SQL flavors and Kubernetes third party resources.
#
# See the documentation (https://dexidp.io/docs/storage/) for further information.
storage:
type: sqlite3
config:
file: ":memory:"

# Configuration for the HTTP endpoints.
web:
http: 0.0.0.0:5556

oauth2:
# Uncomment the passwordConnector to use a specific connector for password grants
passwordConnector: local

# Instead of reading from an external storage, use this list of clients.
#
# If this option isn't chosen clients may be added through the gRPC API.
staticClients:
- id: example-app
redirectURIs:
- "http://dex:5555/callback"
name: "Example App"
secret: ZXhhbXBsZS1hcHAtc2VjcmV0

connectors:
- type: mockCallback
id: mock
name: Example

# Let dex keep a list of passwords which can be used to login to dex.
enablePasswordDB: true

# A static list of passwords to login the end user. By identifying here, dex
# won't look in its underlying storage for passwords.
#
# If this option isn't chosen users may be added through the gRPC API.
staticPasswords:
- email: "[email protected]"
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
# Important bit here, the test pass because this username equals a pre-set MinIO policy (and claim name
# is set to name above).
username: "readwrite"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
104 changes: 78 additions & 26 deletions tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

use arrow_flight::FlightClient;
use assert_cmd::prelude::*;
use aws_credential_types::provider::SharedCredentialsProvider;
use aws_credential_types::Credentials;
use aws_sdk_sts::config::ProvideCredentials;
use aws_sdk_sts::Client;
use rstest::fixture;
use seafowl::config::schema::{load_config_from_string, SeafowlConfig};
use std::collections::HashMap;
Expand Down Expand Up @@ -145,30 +146,81 @@ async fn get_addr() -> SocketAddr {
.unwrap()
}

// Get temporary creds for a specific role in MinIO
async fn get_sts_creds() -> (String, String, String) {
let root_creds = Credentials::from_keys("minioadmin", "minioadmin", None);

let config = aws_config::SdkConfig::builder()
.region(aws_config::Region::new("us-east-1"))
.endpoint_url("http://localhost:9000")
.time_source(aws_smithy_async::time::SystemTimeSource::new())
.build();

let provider = aws_config::sts::AssumeRoleProvider::builder("test-user")
.session_name("test-session")
.configure(&config)
.build_from_provider(root_creds)
.await;

let creds = provider
.provide_credentials()
.await
.expect("MinIO STS credentials provided");
enum AssumeRoleTarget {
MinioUser,
DexOIDC,
}

(
creds.access_key_id().to_string(),
creds.secret_access_key().to_string(),
creds.session_token().expect("Token present").to_string(),
)
// Get temporary creds for a specific role in MinIO. The hard-coded configs stem from the
// values used in the `docker-compose.yml` file.
async fn get_sts_creds(role: AssumeRoleTarget) -> (String, String, String) {
match role {
AssumeRoleTarget::MinioUser => {
let root_creds = Credentials::from_keys("minioadmin", "minioadmin", None);

let config = aws_config::SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(root_creds))
.region(aws_config::Region::new("us-east-1"))
.endpoint_url("http://localhost:9000")
.build();

let creds = Client::new(&config)
.assume_role()
.role_arn("test-user")
.send()
.await
.unwrap()
.credentials
.expect("MinIO STS credentials provided");

(
creds.access_key_id,
creds.secret_access_key,
creds.session_token,
)
}
AssumeRoleTarget::DexOIDC => {
let client = reqwest::Client::new();

// Get a token from Dex
let url = "http://localhost:5556/dex/token";
let params = [
("grant_type", "password"),
("client_id", "example-app"),
("client_secret", "ZXhhbXBsZS1hcHAtc2VjcmV0"),
("username", "[email protected]"),
("password", "password"),
("scope", "groups openid profile email offline_access"),
];
let response = client.post(url).form(&params).send().await.unwrap();

let status = response.status();
let body = response.text().await.unwrap();
assert_eq!(status, 200, "Dex token request failed: {body}");
let res: serde_json::Value = serde_json::from_str(&body).unwrap();
let dex_token = res.get("access_token").expect("token present");

// Exchange Dex token for valid MinIO STS credentials using the AssumeRoleWithWebIdentity
// action.
let config = aws_config::SdkConfig::builder()
.region(aws_config::Region::new("us-east-1"))
.endpoint_url("http://localhost:9000")
.build();

let creds = Client::new(&config)
.assume_role_with_web_identity()
.web_identity_token(dex_token.as_str().unwrap())
.send()
.await
.unwrap()
.credentials
.expect("MinIO STS credentials provided");

(
creds.access_key_id,
creds.secret_access_key,
creds.session_token,
)
}
}
}
2 changes: 0 additions & 2 deletions tests/statements/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ bucket = "seafowl-test-bucket"
r#"type = "s3"
endpoint = "http://127.0.0.1:9000"
bucket = "seafowl-test-bucket-public"
[object_store.cache_properties]
ttl = 30
"#
.to_string(),
None,
Expand Down

0 comments on commit 3ad15f7

Please sign in to comment.