WebDisk
Object Storage

STS in practice: access to S3 with a company account (SSO), without static keys

Data publikacji:

WebDisk Blog · category: Security · reading time: ~11 minutes

In short:- Static S3 keys are secrets with no expiry date – they leak, nobody rotates them, and an employee leaving the company does not invalidate them.- STS (Security Token Service) issues temporary credentials: they work for an hour or a few, and then become worthless.- Combining STS with company login (SSO) means you reach S3 storage with your company account – and permissions and access are managed in one place. >Not working with a terminal? Skip the code examples – the description of how it works and the section about WebDisk give you the full picture.

Imagine that every employee is handed a master key to the office on their first day – cut at the shop around the corner, with no record kept, valid indefinitely. When someone leaves the company, the key stays in their drawer. When someone loses it, you change the locks… in theory, because in practice nobody remembers how many keys are circulating out there.

Sounds absurd? And yet that is exactly how most S3 deployments work: a static key pair (access key + secret key) generated once, pasted into scripts, configurations and notes – and living on for years. In this article we show an alternative that the cloud world has been using for a long time, and which is still surprisingly little known: STS, a service that issues temporary credentials, and its most interesting application – entering S3 with a company account through SSO. At the end we describe how we built an entire product on top of it at WebDisk.

The problem: a secret that never expires

Classic S3 access comes down to a pair: a key identifier (access key) and a secret (secret key). Technically it is a simple and convenient solution – which is why it caught on. But it has four flaws that only grow over time:

  • A leak is a matter of time. The key goes into a script, the script into a repository, the repository onto GitHub. Bots scanning public repositories find such secrets within minutes of publication.
  • Rotation hurts, so nobody does it. Replacing a key means updating every place it was pasted into – and nobody knows how many there are. The result: keys live for years.
  • An employee leaving changes nothing. The account in the company directory gets blocked, but the S3 key is a separate entity – it keeps working until someone happens to remember it during an audit.
  • One key, many users. In practice teams share keys, so the event log only says "someone with key X" – with no information about who.

Each of these flaws has a common source: the credential is eternal and detached from a person's identity. There is only one way to fix this – credentials must be short-lived and issued on the basis of a real, corporate identity.

What STS is: a pass instead of a key

STS (Security Token Service) is a service from the AWS API family – also implemented in Ceph RGW, the engine of our platform – that does one thing: it issues temporary access credentials. Instead of an eternal key pair you get three things:

  • a temporary access key and secret key – they look and work like ordinary S3 keys,
  • a session token – an additional element attached to every request,
  • and an expiry date – once it passes, all three become worthless.

The best analogy is a visitor pass in an office building: reception checks who you are, prints a badge valid until 5 p.m., and that's it. Nobody has to take it back on the way out, there is no need to change the locks if you lose it – it simply stops working tomorrow.

The key question is: on what basis does STS issue the pass? There are several ways (the AssumeRole* family of calls), but we are interested in the most elegant one: on the basis of proof from a company login.

SSO + S3, that is AssumeRoleWithWebIdentity

If your company has SSO (single sign-on – one login for all applications), then every employee already has a digital identity: an account in the company directory, with a password, MFA and group membership. The OpenID Connect (OIDC) standard, on which most modern SSO systems run, issues a so-called id_token after login – a digitally signed "ID card": who logged in, where and for which application.

An STS call named AssumeRoleWithWebIdentity lets you exchange this proof for temporary S3 credentials. Step by step:

  1. You log in with your company account (SSO: password, MFA – just like for any other application). You receive an id_token.
  2. You present the id_token to the STS service, indicating the IAM role (IAM – Identity and Access Management, the system of roles and permissions in the AWS/S3 world) that you want to assume. A role is the pass template that reception keeps in a binder: a set of policies describing what the holder is allowed to do (e.g. "read and write buckets starting with firma-").
  3. STS verifies the proof: it checks the token's signature with the issuer (your SSO), its validity, and whether the role's trust policy allows holders of such tokens to assume that role.
  4. You get all three credentials, valid for, say, an hour – and you work with S3 exactly as before: aws CLI, rclone, boto3, any S3-compatible tool.
  5. After expiry you log in again (or the tool refreshes the credentials by itself – more on that below).

Note the division of roles: SSO answers the question "who are you", the IAM role – "what are you allowed to do", and STS is the reception desk that checks the former and prints the pass according to the template. This separation is the heart of the whole design:

  • identities are managed by the IT department in one place (the company directory) – blocking an account cuts off access to everything at once,
  • permissions to data are managed by the data owner – through the role's policy, not by handing out keys,
  • and in the event log every session has a name that can be tied to a person. One caveat: the session name is declared by the caller, so credible accountability requires it to be enforced by the trust policy or by the broker that performs the exchange (that is what we do at WebDisk – more on that below).

A technical aside: AssumeRoleWithWebIdentity is one of the few calls in the AWS API world that requires no prior credentials – you do not sign it with a key, because your authentication is the id_token itself. That is why the whole chain can work for a user who does not have and never had static S3 keys.

What an IAM role looks like

A role consists of two parts that are worth distinguishing, because they answer two different questions. The examples feature an ARN (Amazon Resource Name) – a unique identifier for a resource in the AWS/S3 world: a role, a bucket or an identity issuer.

The trust policywho can assume the role. It points to a registered OIDC issuer (your SSO) and to conditions, e.g. that the token must be issued for a specific application:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::<konto>:oidc-provider/sso.firma.example/realms/firma" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": { "StringEquals": { "sso.firma.example/realms/firma:aud": "s3-firma" } }
  }]
}

Permission policieswhat the holder of the role can do. An ordinary IAM document, e.g.:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:ListBucket", "s3:GetObject", "s3:PutObject"],
    "Resource": ["arn:aws:s3:::firma-*"]
  }]
}

The exact syntax of the condition keys differs between providers (AWS, Ceph RGW) – but the pattern is always the same: trust in the identity issuer + the boundaries of permissions.

Do it yourself: STS in the terminal and in code

Before you start. You need three things from the platform administrator: the address of the S3/STS endpoint, the ARN of the role you may assume, and a valid id_token from your SSO. The last one is in practice the hardest: the token is issued by your SSO after login, and you obtain it with your own OIDC integration (the Authorization Code + PKCE flow) or with tools such as oidc-agent. In the WebDisk service you have to do none of this – the self-service panel logs you in through SSO and performs the whole exchange for you (more on that in the next section); the examples below show what happens under the hood and are a starting point for integrators.

Exchanging the token for credentials in the aws CLI:

aws sts assume-role-with-web-identity \
  --role-arn "arn:aws:iam::<konto>:role/firma-s3" \
  --role-session-name "jkowalski" \
  --web-identity-token file://id_token.jwt \
  --duration-seconds 3600 \
  --endpoint-url https://s3.twoj-dostawca.example

In response you get a Credentials block with all three credentials and the expiry date. Put them into environment variables – and from that moment on every S3-compatible tool works as usual:

export AWS_ACCESS_KEY_ID="ASIA...tymczasowy"
export AWS_SECRET_ACCESS_KEY="...tymczasowy-sekret..."
export AWS_SESSION_TOKEN="...dlugi-token-sesji..."

aws s3 ls s3://firma-dokumenty/ --endpoint-url https://s3.twoj-dostawca.example

Even more conveniently: the AWS CLI and SDKs recognize the variables AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE. When you set them, the token exchange and credential refresh happen automatically – the tool calls STS by itself when the previous pass expires:

export AWS_ROLE_ARN="arn:aws:iam::<konto>:role/firma-s3"
export AWS_WEB_IDENTITY_TOKEN_FILE="$HOME/.config/firma/id_token.jwt"
export AWS_ENDPOINT_URL_STS="https://s3.twoj-dostawca.example" # without this the CLI will call Amazon's STS!

aws s3 ls s3://firma-dokumenty/ --endpoint-url https://s3.twoj-dostawca.example

Two traps you need to know:

  • --endpoint-url applies only to the S3 call itself. The automatic token exchange is a separate, hidden STS call – without an endpoint specified, the tool will direct it to Amazon's default STS (sts.<region>.amazonaws.com): the example will fail with an error, and your company id_token will end up at a third-party service. That is why, with an external provider, you should always also set AWS_ENDPOINT_URL_STS (at providers based on Ceph RGW, including ours, S3 and STS live at the same address). This requires up-to-date tools with support for per-service endpoints: aws CLI v2 ≥ 2.13 (v1 ≥ 1.29), boto3 ≥ 1.28; older SDKs do not support this setting. Explicit calls – like the first example above – are correct regardless of the version.
  • The automation is only as fresh as the token in the file. On every refresh the tool reads the token file anew – and OIDC id_tokens are short-lived (typically minutes rather than hours), usually shorter than the pass issued on their basis. With a manually saved token, the refresh will therefore succeed once or twice, and then STS will respond with an ExpiredToken error. For work longer than the token's lifetime you need something that renews the token itself: an OIDC integration with a refresh token, logging in again – or an environment that rotates the file for you (this is how Kubernetes works, for example, where the kubelet regularly replaces the pod's service token).

The same thing programmatically in Python (boto3):

import boto3

sts = boto3.client("sts", endpoint_url="https://s3.twoj-dostawca.example")

resp = sts.assume_role_with_web_identity(
    RoleArn="arn:aws:iam::<konto>:role/firma-s3",
    RoleSessionName="jkowalski",
    WebIdentityToken=open("id_token.jwt").read(),
    DurationSeconds=3600,
)
c = resp["Credentials"]

s3 = boto3.client(
    "s3", endpoint_url="https://s3.twoj-dostawca.example",
    aws_access_key_id=c["AccessKeyId"],
    aws_secret_access_key=c["SecretAccessKey"],
    aws_session_token=c["SessionToken"],
)
listing = s3.list_objects_v2(Bucket="firma-dokumenty")
print([o["Key"] for o in listing.get("Contents", [])])

And if you prefer rclone: you provide the temporary credentials just like ordinary ones, adding the session token (session_token in the configuration of the S3 remote, or env_auth = true so that rclone reads the three environment variables shown above).

What it gives you – and what it does not

An honest assessment looks like this:

You gain:

  • No eternal secrets. There is nothing to paste into a repository for years; a credential leak is dangerous for an hour, not for a decade.
  • Central offboarding. Blocking an account in SSO cuts off the ability to obtain new credentials – for all systems at once, in one place.
  • Accountability. Every session has a name tied to a person (if the broker or the policy enforces it); the event log stops being anonymous.
  • Boundaries set by policy, not by a key. Changing permissions means editing the role's policy – immediate for all subsequent sessions, without replacing secrets.

You need to know:

  • A pass that has already been issued stays valid until it expires. Blocking an account in SSO does not invalidate credentials issued a minute earlier – they will expire on their own, but until then they work. That is exactly why a short lifetime is set: an hour, not a day.
  • Tools have to be able to refresh themselves. A script running longer than the validity of the credentials has to renew them – the automation with AWS_WEB_IDENTITY_TOKEN_FILE will do it for you provided that the file contains a fresh id_token (see the traps above).
  • Automated processes are a different story. STS with SSO solves the problem of people. Service processes (backup, integrations) do not log in with a password and MFA – separate mechanisms are used for them: service accounts with minimal permissions, machine identities, and static keys, if they must exist, are kept in a secrets manager and rotated automatically.

How we do it at WebDisk: Tenant Manager

Everything we described above comes together in our S3 for organizations service, managed by WebDisk Tenant Manager. While designing it, we made a decision that brings clarity to the whole security model: end users have no static S3 keys at all – access is exclusively through STS.

Every organization (tenant) gets the full package:

  • its own identity directory – a dedicated area in our SSO where the organization's administrator creates accounts for their own people; this is where login happens and where the accounts of departing employees are blocked,
  • a strictly isolated account on the Ceph platform – the buckets, roles and policies of one organization are invisible and inaccessible to others; the isolation is completed by a dedicated bucket name prefix,
  • IAM roles that trust only that organization's directory – the trust policy points to a specific OIDC issuer, so a token from another company's directory is worthless,
  • a graphical IAM policy editor – the organization's administrator manages permissions from the panel, without writing JSON by hand,
  • and its own SSE-KMS encryption key – data at rest is encrypted with the organization's key, which can be rotated or disabled from the panel (we wrote about the encryption variants in S3 in the previous article in this series).

For an employee of the organization it looks like this: they go to the self-service page, log in with their company account (the organization's SSO), and the panel – acting as a broker – performs the AssumeRoleWithWebIdentity described above on their behalf and shows ready-to-use credentials valid for one hour, along with copy-paste configuration snippets: environment variables, aws CLI configuration, rclone configuration. The broker also assigns the session name based on the logged-in identity – so accountability does not depend on the caller's goodwill. One click refreshes the pass. Zero keys to remember, zero secrets to rotate, full compatibility with every S3 tool.

The S3 for organizations service is currently in early access – if you want to test this model at your company, write to us and we will prepare an environment for your organization.

It is worth highlighting the difference compared with our end-user applications. In the web applications WebDisk Files or WebDisk Send you never touch S3 credentials – access to the storage is managed for you by the application. The STS offering is for the opposite situation: when your people and your own tools are to talk to S3 directly – analytics scripts, rclone, integrations – and you want them to do so with your company identity, not with some phantom key from someone's notes.

Frequently asked questions

Do I have to change my tools in order to use STS? No. Temporary credentials are ordinary S3 credentials plus a session token – they are supported by the aws CLI, all SDKs, rclone and practically every S3-compatible tool. Only the way of obtaining them changes.

What happens when an employee leaves the company? The administrator blocks their account in the directory (SSO) – and that is the end of the procedure. They will no longer be able to obtain new credentials. The last issued pass will expire on its own within an hour at the latest.

Does STS work only in AWS? No – it is an API defined by AWS that has become a de facto standard and is also implemented by other platforms, including Ceph RGW, on which our object platform runs. From the tools' perspective there is no difference: the same calls, the same credential format.

What is the difference between AssumeRole and AssumeRoleWithWebIdentity? AssumeRole requires the caller to already have some credentials (a key or another role) – it is used for switching between roles. AssumeRoleWithWebIdentity authenticates with the OIDC token from SSO alone – and that is why it is the right choice when the starting point is a company account rather than an existing key.

Isn't an hour of validity too short? In practice it is not: new credentials are obtained with a single click in the panel (or automatically, if your OIDC integration takes care of a fresh id_token in the file), and the short lifetime is exactly what makes a leak a minor problem. It is a deliberate trade-off – the longer the pass, the longer it works in the wrong hands.

I have automation and service processes. Can they use STS too? The calls themselves – yes, but an automated process will not log in with a password and MFA, so it needs a different source of identity. That is a separate topic (machine identities, service accounts); in a typical deployment people use SSO + STS, while the few service accounts have minimal permissions and keys under the care of a secrets manager.

Summary

Static S3 keys are a security debt that grows with every month: secrets multiply, knowledge about them is lost, and departures change nothing. STS reverses this model – credentials become short-lived, personal and issued on the basis of a company login:

  • SSO says who you are (and lets that account be blocked centrally),
  • the IAM role says what you are allowed to do (and lets that be changed with a single policy),
  • STS exchanges one for the other – for an hour, not forever.

At WebDisk we have made this the foundation of the S3 for organizations service: a separate identity directory per organization, strict isolation, a policy editor and self-service temporary credentials – without a single static key in the users' hands. The service is in early access – if you want to see this model at your company, write to us. And if you are interested in how we encrypt data at rest, take a look at the previous article in the series about SSE-S3, SSE-KMS and SSE-C.


This article is part of a series about data security in WebDisk services. You can run the examples on any S3 platform with STS and OIDC support. The --endpoint-url parameter in the aws CLI points to your provider's S3 endpoint, and with automatic token exchange the additional AWS_ENDPOINT_URL_STS points to its STS endpoint – in Ceph RGW both APIs live at a single address, in AWS they are two different services.