AWS Viewer Integrations with Python

Problem

You are building an interactive Python application which needs to assume an AWS IAM role in order to access a private AWS resource, like S3. The content must authenticate to an identity provider configured to be validated by AWS using the viewer’s credentials. For example, you might need to read data from an IAM-role-restricted S3 bucket.

Solution

The examples below illustrate how to use the OAuth integrations feature of Posit Connect to delegate authorization for access to an external provider’s resources. In this case, we are focusing on S3 within AWS. These examples use the viewer’s OIDC token to assume an AWS IAM role that provides temporary session credentials to the content. These credentials are used to call the AWS API on behalf of the content viewer.

Ensure the following dependencies are installed:

  • boto3
  • shiny
  • posit-sdk>=0.9.0

When publishing the content to Connect make sure the following environment variables are set for the deployed content:

  • AWS_REGION
  • AWS_BUCKET_NAME
  • AWS_S3_FILEPATH
app.py
# -*- coding: utf-8 -*-
# mypy: ignore-errors
import os

import io
import pandas as pd
import boto3
from posit.connect import Client
from posit.connect.external import aws
from shiny import App, Inputs, Outputs, Session, render, ui

app_ui = ui.page_fluid(ui.h2("S3 Data Viewer"), ui.output_data_frame("data_grid"))


def server(i: Inputs, o: Outputs, session: Session):
    """
    Shiny for Python example application that shows user information and
    the first few rows from a table hosted in Databricks.
    """
    # Obtain AWS credentials and start a session:
    # 1. If running on Posit Connect, perform a credential exchange with the
    #    user session token that is provided within the session header.
    # 2. If running locally, initialize a session using your preferred method.
    #    This example assumes the use of the aws cli to authenticate.
    if os.getenv("POSIT_PRODUCT") == "CONNECT":
        client = Client()
        session_token = session.http_conn.headers.get(
            "Posit-Connect-User-Session-Token"
        )
        aws_credentials = aws.get_credentials(client, session_token)
        aws_session = boto3.Session(
            aws_access_key_id=aws_credentials["aws_access_key_id"],
            aws_secret_access_key=aws_credentials["aws_secret_access_key"],
            aws_session_token=aws_credentials["aws_session_token"],
        )
    else:
        # Use default credentials from environment/config
        aws_session = boto3.Session()

    # Create an S3 client using the AWS session
    s3 = aws_session.client("s3")

    # Define bucket and key (file path)
    bucket_name = os.getenv("AWS_BUCKET_NAME")
    key = os.getenv("AWS_S3_FILEPATH")

    # Get the object from S3 and read it as a DataFrame
    obj = s3.get_object(Bucket=bucket_name, Key=key)
    csv_content = obj["Body"].read()
    df = pd.read_csv(io.BytesIO(csv_content))

    @render.data_frame
    def data_grid():
        # Return the DataFrame to display in the data grid
        return df


app = App(app_ui, server)