or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bucket.mdclient.mdcluster.mdimage.mdindex.mdmultipart.mdobject.mdrtmp.mdsts.md
tile.json

sts.mddocs/

Security Token Service (STS)

The Security Token Service (STS) provides temporary credentials for secure, time-limited access to OSS resources. It enables fine-grained access control through role assumption and policy-based permissions.

STS Client Creation

STS Client Factory

function STS(options: STSOptions): STSClient;

interface STSOptions {
  accessKeyId: string;
  accessKeySecret: string;
  endpoint?: string;
  apiVersion?: string;
  timeout?: number | number[];
}

Usage

const OSS = require('ali-oss');

// Create STS client
const sts = new OSS.STS({
  accessKeyId: 'your-access-key-id',
  accessKeySecret: 'your-access-key-secret'
});

Role Assumption

Assume Role

async function assumeRole(role: string, policy?: string, expiration?: number, session?: string, options?: RequestOptions): Promise<AssumeRoleResult>;

interface AssumeRoleResult {
  credentials: STSCredentials;
  assumedRoleUser: AssumedRoleUser;
  res: ResponseInfo;
}

interface STSCredentials {
  accessKeyId: string;
  accessKeySecret: string;
  securityToken: string;
  expiration: Date;
}

interface AssumedRoleUser {
  arn: string;
  assumedRoleId: string;
}

Parameters

  • role: ARN of the role to assume (e.g., acs:ram::123456789012:role/MyRole)
  • policy: Optional JSON policy string to further restrict permissions (must be valid JSON)
  • expiration: Token expiration time in seconds (900-43200, default: 3600)
  • session: Optional session name for identification (defaults to 'app' if not provided)

Policy Validation

The STS service validates policy documents as JSON strings. Policies must:

  • Be valid JSON format
  • Follow Alibaba Cloud RAM policy syntax
  • Not exceed maximum policy size limits
  • Contain valid actions, resources, and conditions

Usage Examples

Basic Role Assumption

// Assume a role for temporary access
const result = await sts.assumeRole(
  'acs:ram::123456789012:role/OSS-ReadOnly',
  null,        // No additional policy restrictions
  3600,        // 1 hour expiration
  'MySession'  // Session identifier
);

console.log('Temporary credentials:');
console.log('Access Key ID:', result.credentials.accessKeyId);
console.log('Access Key Secret:', result.credentials.accessKeySecret);
console.log('Security Token:', result.credentials.securityToken);
console.log('Expires at:', result.credentials.expiration);

// Use temporary credentials with OSS client
const ossClient = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: result.credentials.accessKeyId,
  accessKeySecret: result.credentials.accessKeySecret,
  stsToken: result.credentials.securityToken,
  bucket: 'my-bucket'
});

Role Assumption with Policy Restrictions

// Define a restrictive policy
const restrictivePolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:GetObject'],
      Resource: ['acs:oss:*:*:my-bucket/public/*']
    }
  ]
};

// Assume role with additional restrictions
const result = await sts.assumeRole(
  'acs:ram::123456789012:role/OSS-FullAccess',
  JSON.stringify(restrictivePolicy),
  1800,  // 30 minutes
  'RestrictedSession'
);

// This client can only read objects in the 'public/' prefix
const restrictedClient = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: result.credentials.accessKeyId,
  accessKeySecret: result.credentials.accessKeySecret,
  stsToken: result.credentials.securityToken,
  bucket: 'my-bucket'
});

Common STS Patterns

Temporary Upload Access

// Policy for upload-only access to specific prefix
const uploadPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:PutObject'],
      Resource: [`acs:oss:*:*:my-bucket/uploads/${userId}/*`]
    }
  ]
};

const uploadCredentials = await sts.assumeRole(
  'acs:ram::123456789012:role/OSS-Upload',
  JSON.stringify(uploadPolicy),
  900,  // 15 minutes for upload
  `upload-${userId}`
);

// Client with upload-only permissions
const uploadClient = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: uploadCredentials.credentials.accessKeyId,
  accessKeySecret: uploadCredentials.credentials.accessKeySecret,
  stsToken: uploadCredentials.credentials.securityToken,
  bucket: 'my-bucket'
});

Temporary Download Access

// Policy for read-only access to specific objects
const downloadPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:GetObject'],
      Resource: [
        'acs:oss:*:*:my-bucket/documents/report.pdf',
        'acs:oss:*:*:my-bucket/images/photo.jpg'
      ]
    }
  ]
};

const downloadCredentials = await sts.assumeRole(
  'acs:ram::123456789012:role/OSS-ReadOnly',
  JSON.stringify(downloadPolicy),
  3600,  // 1 hour
  `download-${sessionId}`
);

Cross-Account Access

// Assume role in different account
const crossAccountCredentials = await sts.assumeRole(
  'acs:ram::987654321098:role/CrossAccountAccess',
  null,
  7200,  // 2 hours
  'CrossAccountSession'
);

// Access resources in different account
const crossAccountClient = new OSS({
  region: 'oss-cn-beijing',
  accessKeyId: crossAccountCredentials.credentials.accessKeyId,
  accessKeySecret: crossAccountCredentials.credentials.accessKeySecret,
  stsToken: crossAccountCredentials.credentials.securityToken,
  bucket: 'partner-bucket'
});

Advanced STS Usage

Token Refresh Pattern

class STSTokenManager {
  constructor(stsClient, role, policy, expiration = 3600) {
    this.stsClient = stsClient;
    this.role = role;
    this.policy = policy;
    this.expirationSeconds = expiration;
    this.credentials = null;
    this.refreshTimer = null;
  }
  
  async getCredentials() {
    if (!this.credentials || this.isExpiringSoon()) {
      await this.refreshCredentials();
    }
    return this.credentials;
  }
  
  async refreshCredentials() {
    try {
      const result = await this.stsClient.assumeRole(
        this.role,
        this.policy,
        this.expirationSeconds,
        `session-${Date.now()}`
      );
      
      this.credentials = result.credentials;
      this.scheduleRefresh();
      
      console.log('STS credentials refreshed');
    } catch (error) {
      console.error('Failed to refresh STS credentials:', error);
      throw error;
    }
  }
  
  isExpiringSoon(bufferMinutes = 5) {
    if (!this.credentials) return true;
    
    const expirationTime = new Date(this.credentials.expiration);
    const now = new Date();
    const bufferMs = bufferMinutes * 60 * 1000;
    
    return (expirationTime.getTime() - now.getTime()) < bufferMs;
  }
  
  scheduleRefresh() {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }
    
    // Refresh 10 minutes before expiration
    const refreshTime = (this.expirationSeconds - 600) * 1000;
    this.refreshTimer = setTimeout(() => {
      this.refreshCredentials().catch(console.error);
    }, refreshTime);
  }
  
  cleanup() {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }
  }
}

// Usage
const tokenManager = new STSTokenManager(
  sts,
  'acs:ram::123456789012:role/OSS-ReadWrite',
  null,
  3600
);

// Get current credentials (will refresh if needed)
const credentials = await tokenManager.getCredentials();

const ossClient = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: credentials.accessKeyId,
  accessKeySecret: credentials.accessKeySecret,
  stsToken: credentials.securityToken,
  bucket: 'my-bucket'
});

Browser-Safe STS

// Server-side: Generate STS token for browser use
async function generateBrowserSTS(userId, permissions) {
  const policy = {
    Version: '1',
    Statement: [
      {
        Effect: 'Allow',
        Action: permissions.actions,
        Resource: permissions.resources,
        Condition: {
          StringEquals: {
            'oss:x-oss-meta-user-id': userId
          }
        }
      }
    ]
  };
  
  const result = await sts.assumeRole(
    'acs:ram::123456789012:role/BrowserAccess',
    JSON.stringify(policy),
    1800,  // 30 minutes for browser
    `browser-${userId}`
  );
  
  // Return only necessary information to browser
  return {
    accessKeyId: result.credentials.accessKeyId,
    accessKeySecret: result.credentials.accessKeySecret,
    securityToken: result.credentials.securityToken,
    expiration: result.credentials.expiration,
    region: 'oss-cn-hangzhou',
    bucket: 'user-uploads'
  };
}

// Client-side: Use STS credentials
const browserCredentials = await fetch('/api/sts-token').then(r => r.json());

const browserOSS = new OSS({
  region: browserCredentials.region,
  accessKeyId: browserCredentials.accessKeyId,
  accessKeySecret: browserCredentials.accessKeySecret,
  stsToken: browserCredentials.securityToken,
  bucket: browserCredentials.bucket
});

Policy Examples

Read-Only Access

const readOnlyPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: [
        'oss:GetObject',
        'oss:GetObjectMeta',
        'oss:ListObjects'
      ],
      Resource: ['acs:oss:*:*:my-bucket/*']
    }
  ]
};

Upload-Only Access

const uploadOnlyPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:PutObject'],
      Resource: ['acs:oss:*:*:my-bucket/uploads/*'],
      Condition: {
        StringEquals: {
          'oss:x-oss-object-type': 'Normal'
        }
      }
    }
  ]
};

Time-Based Access

const timeBasedPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:GetObject'],
      Resource: ['acs:oss:*:*:my-bucket/*'],
      Condition: {
        DateGreaterThan: {
          'acs:CurrentTime': '2024-01-01T00:00:00Z'
        },
        DateLessThan: {
          'acs:CurrentTime': '2024-12-31T23:59:59Z'
        }
      }
    }
  ]
};

IP-Restricted Access

const ipRestrictedPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:*'],
      Resource: ['acs:oss:*:*:my-bucket/*'],
      Condition: {
        IpAddress: {
          'acs:SourceIp': ['192.168.1.0/24', '10.0.0.0/8']
        }
      }
    }
  ]
};

Error Handling

STS-Specific Errors

try {
  const result = await sts.assumeRole(role, policy, expiration, session);
  return result.credentials;
} catch (error) {
  if (error.code === 'NoSuchRole') {
    console.error('Role does not exist or access denied');
  } else if (error.code === 'MalformedPolicyDocument') {
    console.error('Invalid policy format');
  } else if (error.code === 'InvalidParameterValue') {
    console.error('Invalid parameter (check expiration time, role ARN format)');
  } else if (error.code === 'TokenRequestDenied') {
    console.error('Permission denied for role assumption');
  } else {
    console.error('STS error:', error.message);
  }
  throw error;
}

Credential Validation

function validateCredentials(credentials) {
  if (!credentials) {
    throw new Error('No credentials provided');
  }
  
  if (!credentials.accessKeyId || !credentials.accessKeySecret || !credentials.securityToken) {
    throw new Error('Incomplete credentials');
  }
  
  const now = new Date();
  const expiration = new Date(credentials.expiration);
  
  if (expiration <= now) {
    throw new Error('Credentials have expired');
  }
  
  return true;
}

Implementation Details

Authentication and Signatures

The STS service uses HMAC-SHA1 signature algorithm for request authentication:

  • Signature Method: HMAC-SHA1
  • String Encoding: UTF-8 with proper URL encoding for special characters
  • Request Format: Query string parameters with signature validation

Request Parameters

STS requests include:

  • AccessKeyId: Primary access key for authentication
  • Timestamp: UTC timestamp for request validity
  • SignatureNonce: Unique identifier to prevent replay attacks
  • Signature: HMAC-SHA1 hash of the canonical request string

Error Codes and Retry Behavior

Common STS error codes:

  • NoSuchRole: Role ARN is invalid or inaccessible
  • MalformedPolicyDocument: Policy JSON is malformed
  • InvalidParameterValue: Parameter values outside acceptable ranges
  • TokenRequestDenied: Insufficient permissions for role assumption
  • RequestTimeout: Network timeout during request processing

Retry behavior includes exponential backoff for transient errors.

Security Best Practices

Principle of Least Privilege

// Grant minimal necessary permissions
const minimalPolicy = {
  Version: '1',
  Statement: [
    {
      Effect: 'Allow',
      Action: ['oss:GetObject'],  // Only what's needed
      Resource: [`acs:oss:*:*:bucket/user-${userId}/*`],  // Specific resources
      Condition: {
        StringEquals: {
          'oss:x-oss-meta-owner': userId  // Additional restrictions
        }
      }
    }
  ]
};

Short-Lived Tokens

// Use short expiration times
const credentials = await sts.assumeRole(
  role,
  policy,
  900,  // 15 minutes minimum, use for specific operations
  session
);

Session Naming

// Use descriptive session names for auditing
const sessionName = `${operation}-${userId}-${timestamp}`;
const credentials = await sts.assumeRole(role, policy, expiration, sessionName);

Token Storage

// Never store STS tokens in persistent storage
// Use in-memory storage with automatic refresh
class SecureTokenStorage {
  constructor() {
    this.tokens = new Map();
  }
  
  set(key, credentials) {
    // Store with expiration check
    this.tokens.set(key, {
      credentials,
      storedAt: new Date()
    });
    
    // Auto-cleanup expired tokens
    setTimeout(() => {
      this.tokens.delete(key);
    }, this.getTimeToExpiration(credentials));
  }
  
  get(key) {
    const entry = this.tokens.get(key);
    if (!entry) return null;
    
    // Check if still valid
    const now = new Date();
    const expiration = new Date(entry.credentials.expiration);
    
    if (expiration <= now) {
      this.tokens.delete(key);
      return null;
    }
    
    return entry.credentials;
  }
  
  getTimeToExpiration(credentials) {
    const now = new Date();
    const expiration = new Date(credentials.expiration);
    return Math.max(0, expiration.getTime() - now.getTime());
  }
}