Skip to content

Commit b6ccf0d

Browse files
authored
Merge pull request #185 from /issues/184-sts
Refactor handlers for temporary authentication tokens
2 parents 67803b5 + 06f2ae6 commit b6ccf0d

File tree

9 files changed

+54
-167
lines changed

9 files changed

+54
-167
lines changed

hub/src/main/java/cloud/katta/protocols/hub/HubUVFVault.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import ch.cyberduck.core.cryptomator.UVFVault;
1818
import ch.cyberduck.core.exception.BackgroundException;
1919
import ch.cyberduck.core.features.Directory;
20+
import ch.cyberduck.core.features.Write;
2021
import ch.cyberduck.core.preferences.PreferencesFactory;
2122
import ch.cyberduck.core.proxy.ProxyFactory;
2223
import ch.cyberduck.core.transfer.TransferStatus;
@@ -89,9 +90,9 @@ public synchronized Path create(final Session<?> session, final String region, f
8990
log.debug("Create vault root directory at {}", secondLevel);
9091
final TransferStatus status = (new TransferStatus()).setRegion(region);
9192

92-
directory.mkdir(dataDir, status);
93-
directory.mkdir(firstLevel, status);
94-
directory.mkdir(secondLevel, status);
93+
directory.mkdir(session._getFeature(Write.class), dataDir, status);
94+
directory.mkdir(session._getFeature(Write.class), firstLevel, status);
95+
directory.mkdir(session._getFeature(Write.class), secondLevel, status);
9596
new ContentWriter(session).write(new Path(secondLevel, "dir.uvf", EnumSet.of(AbstractPath.Type.file)), rootDirUvf);
9697
return home;
9798
}

hub/src/main/java/cloud/katta/protocols/s3/S3AssumeRoleProtocol.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package cloud.katta.protocols.s3;
66

77
import ch.cyberduck.core.CredentialsConfigurator;
8+
import ch.cyberduck.core.Profile;
89
import ch.cyberduck.core.Protocol;
910
import ch.cyberduck.core.s3.S3Protocol;
1011

@@ -19,9 +20,9 @@ public class S3AssumeRoleProtocol extends S3Protocol {
1920
public static final String OAUTH_TOKENEXCHANGE_BASEPATH = "oauth.tokenexchange.basepath";
2021

2122
// STS assume role with web identity from Cyberduck core (AWS + MinIO)
22-
public static final String S3_ASSUMEROLE_ROLEARN = "s3.assumerole.rolearn";
23-
public static final String S3_ASSUMEROLE_ROLESESSIONNAME = "s3.assumerole.rolesessionname";
24-
public static final String S3_ASSUMEROLE_DURATIONSECONDS = "s3.assumerole.durationseconds";
23+
public static final String S3_ASSUMEROLE_ROLEARN = Profile.STS_ROLE_ARN_PROPERTY_KEY;
24+
public static final String S3_ASSUMEROLE_ROLESESSIONNAME = Profile.STS_ROLE_SESSION_NAME_PROPERTY_KEY;
25+
public static final String S3_ASSUMEROLE_DURATIONSECONDS = Profile.STS_DURATION_SECONDS_PROPERTY_KEY;
2526
public static final String S3_ASSUMEROLE_POLICY = "s3.assumerole.policy";
2627
// STS role chaining (AWS only)
2728
public static final String S3_ASSUMEROLE_ROLEARN_2 = "s3.assumerole.rolearn.2";

hub/src/main/java/cloud/katta/protocols/s3/S3AssumeRoleSession.java

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,20 @@
77
import ch.cyberduck.core.Host;
88
import ch.cyberduck.core.LoginCallback;
99
import ch.cyberduck.core.OAuthTokens;
10-
import ch.cyberduck.core.aws.CustomClientConfiguration;
1110
import ch.cyberduck.core.exception.LoginCanceledException;
12-
import ch.cyberduck.core.http.CustomServiceUnavailableRetryStrategy;
13-
import ch.cyberduck.core.http.ExecutionCountServiceUnavailableRetryStrategy;
1411
import ch.cyberduck.core.oauth.OAuth2AuthorizationService;
1512
import ch.cyberduck.core.oauth.OAuth2RequestInterceptor;
16-
import ch.cyberduck.core.preferences.HostPreferences;
17-
import ch.cyberduck.core.proxy.ProxyFinder;
18-
import ch.cyberduck.core.s3.S3AuthenticationResponseInterceptor;
13+
import ch.cyberduck.core.preferences.HostPreferencesFactory;
1914
import ch.cyberduck.core.s3.S3CredentialsStrategy;
2015
import ch.cyberduck.core.s3.S3Session;
21-
import ch.cyberduck.core.ssl.ThreadLocalHostnameDelegatingTrustManager;
2216
import ch.cyberduck.core.ssl.X509KeyManager;
2317
import ch.cyberduck.core.ssl.X509TrustManager;
24-
import ch.cyberduck.core.sts.STSAssumeRoleCredentialsRequestInterceptor;
18+
import ch.cyberduck.core.sts.STSRequestInterceptor;
2519

26-
import org.apache.commons.lang3.StringUtils;
2720
import org.apache.http.impl.client.HttpClientBuilder;
2821
import org.apache.logging.log4j.LogManager;
2922
import org.apache.logging.log4j.Logger;
3023

31-
import com.amazonaws.auth.AWSStaticCredentialsProvider;
32-
import com.amazonaws.auth.AnonymousAWSCredentials;
33-
import com.amazonaws.client.builder.AwsClientBuilder;
34-
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
35-
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
36-
3724
import static cloud.katta.protocols.s3.S3AssumeRoleProtocol.OAUTH_TOKENEXCHANGE;
3825

3926
public class S3AssumeRoleSession extends S3Session {
@@ -45,18 +32,17 @@ public S3AssumeRoleSession(final Host host, final X509TrustManager trust, final
4532

4633
/**
4734
* Configured by default with credentials strategy using assume role with web identity followed by
48-
* exchaing the retrieved OIDC token with scoped OAuth tokens to obtain temporary credentials from security
35+
* exchanging the retrieved OIDC token with scoped OAuth tokens to obtain temporary credentials from security
4936
* token server (STS)
5037
*
5138
* @see S3AssumeRoleProtocol#OAUTH_TOKENEXCHANGE
5239
* @see S3AssumeRoleProtocol#S3_ASSUMEROLE_ROLEARN_2
5340
*/
5441
@Override
55-
protected S3CredentialsStrategy configureCredentialsStrategy(final ProxyFinder proxy, final HttpClientBuilder configuration,
56-
final LoginCallback prompt) throws LoginCanceledException {
42+
protected S3CredentialsStrategy configureCredentialsStrategy(final HttpClientBuilder configuration, final LoginCallback prompt) throws LoginCanceledException {
5743
if(host.getProtocol().isOAuthConfigurable()) {
5844
final OAuth2RequestInterceptor oauth;
59-
if(new HostPreferences(host).getBoolean(OAUTH_TOKENEXCHANGE)) {
45+
if(HostPreferencesFactory.get(host).getBoolean(OAUTH_TOKENEXCHANGE)) {
6046
oauth = new TokenExchangeRequestInterceptor(configuration.build(), host, prompt);
6147
}
6248
else {
@@ -68,37 +54,16 @@ protected S3CredentialsStrategy configureCredentialsStrategy(final ProxyFinder p
6854
}
6955
log.debug("Register interceptor {}", oauth);
7056
configuration.addInterceptorLast(oauth);
71-
final AWSSecurityTokenService tokenService = AWSSecurityTokenServiceClientBuilder
72-
.standard()
73-
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(host.getProtocol().getSTSEndpoint(), null))
74-
.withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()))
75-
.withClientConfiguration(new CustomClientConfiguration(host,
76-
new ThreadLocalHostnameDelegatingTrustManager(trust, host.getProtocol().getSTSEndpoint()), key))
77-
.build();
78-
final STSAssumeRoleCredentialsRequestInterceptor sts;
79-
if(StringUtils.isNotBlank(host.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLEARN_2))) {
80-
sts = new STSChainedAssumeRoleRequestInterceptor(oauth, this, tokenService, prompt) {
81-
@Override
82-
protected String getWebIdentityToken(final OAuthTokens oauth) {
83-
return oauth.getAccessToken();
84-
}
85-
};
86-
}
87-
else {
88-
sts = new STSAssumeRoleCredentialsRequestInterceptor(oauth, this, tokenService, prompt) {
89-
@Override
90-
protected String getWebIdentityToken(final OAuthTokens oauth) {
91-
return oauth.getAccessToken();
92-
}
93-
};
94-
}
57+
final STSRequestInterceptor sts = new STSChainedAssumeRoleRequestInterceptor(oauth, host, trust, key, prompt) {
58+
@Override
59+
protected String getWebIdentityToken(final OAuthTokens oauth) {
60+
return oauth.getAccessToken();
61+
}
62+
};
9563
log.debug("Register interceptor {}", sts);
9664
configuration.addInterceptorLast(sts);
97-
final S3AuthenticationResponseInterceptor interceptor = new S3AuthenticationResponseInterceptor(this, sts);
98-
configuration.setServiceUnavailableRetryStrategy(new CustomServiceUnavailableRetryStrategy(host,
99-
new ExecutionCountServiceUnavailableRetryStrategy(interceptor)));
10065
return sts;
10166
}
102-
return super.configureCredentialsStrategy(proxy, configuration, prompt);
67+
return super.configureCredentialsStrategy(configuration, prompt);
10368
}
10469
}

hub/src/main/java/cloud/katta/protocols/s3/STSChainedAssumeRoleRequestInterceptor.java

Lines changed: 27 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,129 +4,59 @@
44

55
package cloud.katta.protocols.s3;
66

7-
import ch.cyberduck.core.AsciiRandomStringService;
7+
import ch.cyberduck.core.Credentials;
88
import ch.cyberduck.core.Host;
99
import ch.cyberduck.core.LoginCallback;
10-
import ch.cyberduck.core.OAuthTokens;
10+
import ch.cyberduck.core.Profile;
1111
import ch.cyberduck.core.TemporaryAccessTokens;
1212
import ch.cyberduck.core.exception.BackgroundException;
13-
import ch.cyberduck.core.exception.LoginFailureException;
1413
import ch.cyberduck.core.oauth.OAuth2RequestInterceptor;
15-
import ch.cyberduck.core.preferences.HostPreferences;
1614
import ch.cyberduck.core.preferences.PreferencesReader;
17-
import ch.cyberduck.core.s3.S3Session;
18-
import ch.cyberduck.core.sts.STSAssumeRoleCredentialsRequestInterceptor;
19-
import ch.cyberduck.core.sts.STSExceptionMappingService;
15+
import ch.cyberduck.core.preferences.ProxyPreferencesReader;
16+
import ch.cyberduck.core.ssl.X509KeyManager;
17+
import ch.cyberduck.core.ssl.X509TrustManager;
18+
import ch.cyberduck.core.sts.STSAssumeRoleWithWebIdentityRequestInterceptor;
2019

2120
import org.apache.commons.lang3.StringUtils;
2221
import org.apache.logging.log4j.LogManager;
2322
import org.apache.logging.log4j.Logger;
2423

25-
import java.util.Collections;
26-
27-
import com.amazonaws.auth.AWSSessionCredentials;
28-
import com.amazonaws.auth.AWSSessionCredentialsProvider;
29-
import com.amazonaws.auth.BasicSessionCredentials;
30-
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
31-
import com.amazonaws.services.securitytoken.model.AWSSecurityTokenServiceException;
32-
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
33-
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
34-
import com.amazonaws.services.securitytoken.model.Tag;
35-
import com.auth0.jwt.JWT;
36-
import com.auth0.jwt.exceptions.JWTDecodeException;
37-
3824
/**
3925
* Assume role with temporary credentials obtained using OIDC token from security token service (STS)
4026
*/
41-
public class STSChainedAssumeRoleRequestInterceptor extends STSAssumeRoleCredentialsRequestInterceptor {
27+
public class STSChainedAssumeRoleRequestInterceptor extends STSAssumeRoleWithWebIdentityRequestInterceptor {
4228
private static final Logger log = LogManager.getLogger(STSChainedAssumeRoleRequestInterceptor.class);
4329

4430
private final Host bookmark;
45-
private final AWSSecurityTokenService service;
46-
47-
public STSChainedAssumeRoleRequestInterceptor(final OAuth2RequestInterceptor oauth, final S3Session session,
48-
final AWSSecurityTokenService service, final LoginCallback prompt) {
49-
super(oauth, session, service, prompt);
50-
this.service = service;
51-
this.bookmark = session.getHost();
52-
}
5331

54-
@Override
55-
public TemporaryAccessTokens authorize(final OAuthTokens oauth) throws BackgroundException {
56-
return this.authorize(oauth, super.authorize(oauth));
32+
public STSChainedAssumeRoleRequestInterceptor(final OAuth2RequestInterceptor oauth, final Host host,
33+
final X509TrustManager trust, final X509KeyManager key,
34+
final LoginCallback prompt) {
35+
super(oauth, host, trust, key, prompt);
36+
this.bookmark = host;
5737
}
5838

5939
/**
6040
* Assume role with previously obtained temporary access token
6141
*
62-
* @param tokens Session credentials
42+
* @param credentials Session credentials
6343
* @return Temporary scoped access tokens
6444
* @throws ch.cyberduck.core.exception.ExpiredTokenException Expired identity
6545
* @throws ch.cyberduck.core.exception.LoginFailureException Authorization failure
6646
*/
67-
public TemporaryAccessTokens authorize(final OAuthTokens oauth, final TemporaryAccessTokens tokens) throws BackgroundException {
68-
final AssumeRoleRequest request = new AssumeRoleRequest()
69-
.withRequestCredentialsProvider(new AWSSessionCredentialsProvider() {
70-
@Override
71-
public AWSSessionCredentials getCredentials() {
72-
return new BasicSessionCredentials(
73-
tokens.getAccessKeyId(),
74-
tokens.getSecretAccessKey(),
75-
tokens.getSessionToken());
76-
}
77-
78-
@Override
79-
public void refresh() {
80-
// nothing to do
81-
}
82-
});
83-
log.debug("Chained assume role for {}", bookmark);
84-
log.debug("Assume role with temporary credentials {}", tokens);
85-
final PreferencesReader preferences = new HostPreferences(bookmark);
86-
if(preferences.getInteger(S3AssumeRoleProtocol.S3_ASSUMEROLE_DURATIONSECONDS) != -1) {
87-
request.setDurationSeconds(preferences.getInteger(S3AssumeRoleProtocol.S3_ASSUMEROLE_DURATIONSECONDS));
88-
}
89-
if(StringUtils.isNotBlank(preferences.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_POLICY))) {
90-
request.setPolicy(preferences.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_POLICY));
91-
}
92-
request.setRoleArn(preferences.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLEARN_2));
93-
final String sub;
94-
final String identityToken = this.getWebIdentityToken(oauth);
95-
try {
96-
sub = JWT.decode(identityToken).getSubject();
97-
}
98-
catch(JWTDecodeException e) {
99-
log.warn("Failure {} decoding JWT {}", e, identityToken);
100-
throw new LoginFailureException("Invalid JWT or JSON format in authentication token", e);
101-
}
102-
if(StringUtils.isNotBlank(preferences.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLESESSIONNAME))) {
103-
request.setRoleSessionName(preferences.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLESESSIONNAME));
104-
}
105-
else {
106-
if(StringUtils.isNotBlank(sub)) {
107-
request.setRoleSessionName(sub);
108-
}
109-
else {
110-
log.warn("Missing subject in decoding JWT {}", identityToken);
111-
request.setRoleSessionName(new AsciiRandomStringService().random());
112-
}
113-
}
114-
if(StringUtils.isNotBlank(preferences.getProperty("s3.assumerole.tag"))) {
115-
request.setTags(Collections.singletonList(new Tag()
116-
.withKey(preferences.getProperty("s3.assumerole.tag"))
117-
.withValue(preferences.getProperty(S3AssumeRoleProtocol.OAUTH_TOKENEXCHANGE_VAULT))));
118-
}
119-
try {
120-
log.debug("Use request {}", request);
121-
final AssumeRoleResult result = service.assumeRole(request);
122-
log.debug("Received assume role result {} for host {}", result, bookmark);
123-
return new TemporaryAccessTokens(result.getCredentials().getAccessKeyId(),
124-
result.getCredentials().getSecretAccessKey(),
125-
result.getCredentials().getSessionToken(),
126-
result.getCredentials().getExpiration().getTime());
127-
}
128-
catch(AWSSecurityTokenServiceException e) {
129-
throw new STSExceptionMappingService().map(e);
130-
}
47+
@Override
48+
public TemporaryAccessTokens assumeRoleWithWebIdentity(final Credentials credentials) throws BackgroundException {
49+
final PreferencesReader settings = new ProxyPreferencesReader(bookmark, credentials);
50+
final TemporaryAccessTokens tokens = super.assumeRoleWithWebIdentity(credentials
51+
.withProperty(Profile.STS_ROLE_ARN_PROPERTY_KEY, settings.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLEARN)));
52+
if(StringUtils.isNotBlank(settings.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLEARN_2))) {
53+
log.debug("Assume role with temporary credentials {}", tokens);
54+
// Assume role with previously obtained temporary access token
55+
return super.assumeRole(credentials.withTokens(tokens)
56+
.withProperty(Profile.STS_ROLE_ARN_PROPERTY_KEY, settings.getProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLEARN_2))
57+
.withProperty(Profile.STS_TAGS_PROPERTY_KEY, String.format("%s=%s", "VaultRequested", settings.getProperty(S3AssumeRoleProtocol.OAUTH_TOKENEXCHANGE_VAULT)))
58+
);
59+
}
60+
return tokens;
13161
}
13262
}

hub/src/main/java/cloud/katta/workflows/CreateVaultService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ static class STSInlinePolicyService {
202202
static STSInlinePolicyService disabled = new STSInlinePolicyService() {
203203
@Override
204204
TemporaryAccessTokens getSTSTokensFromAccessTokenWithCreateBucketInlinePolicy(final String token, final String roleArn, final String roleSessionName, final String stsEndpoint, final String bucketName, final String region, final Boolean bucketAcceleration) {
205-
return new TemporaryAccessTokens(null);
205+
return TemporaryAccessTokens.EMPTY;
206206
}
207207
};
208208

hub/src/test/java/cloud/katta/core/AbstractHubSynchronizeTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ public void test03AddVault(final HubTestConfig config) throws Exception {
270270
assertNotNull(vaults.find(new SimplePathPredicate(bucket)));
271271

272272
assertTrue(hubSession.getFeature(Find.class).find(bucket));
273-
assertEquals(bucket.attributes(), hubSession.getFeature(AttributesFinder.class).find(bucket));
273+
assertEquals(config.vault.region, hubSession.getFeature(AttributesFinder.class).find(bucket).getRegion());
274274

275275
assertNotSame(Vault.DISABLED, vaultRegistry.find(hubSession, bucket));
276276

@@ -304,7 +304,7 @@ public void test03AddVault(final HubTestConfig config) throws Exception {
304304
// directory creation and listing
305305
final Path folder = new Path(vault, new AlphanumericRandomStringService(25).random(), EnumSet.of(Path.Type.directory));
306306

307-
hubSession.getFeature(Directory.class).mkdir(folder, new TransferStatus());
307+
hubSession.getFeature(Directory.class).mkdir(hubSession.getFeature(Write.class), folder, new TransferStatus());
308308
final AttributedList<Path> list = hubSession.getFeature(ListService.class).list(vault, new DisabledListProgressListener());
309309
assertEquals(2, list.size()); // a file and a folder
310310

hub/src/test/java/cloud/katta/testsetup/AbstractHubTest.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,11 @@ public abstract class AbstractHubTest extends VaultTest {
5050
private static final HubTestConfig.VaultSpec minioSTSVaultConfig = new HubTestConfig.VaultSpec("MinIO STS", "732D43FA-3716-46C4-B931-66EA5405EF1C",
5151
null, null, null, "eu-west-1");
5252
private static final HubTestConfig.VaultSpec minioStaticVaultConfig = new HubTestConfig.VaultSpec("MinIO static", "71B910E0-2ECC-46DE-A871-8DB28549677E",
53-
"handmade", "minioadmin", "minioadmin", null);
53+
"handmade", "minioadmin", "minioadmin", "us-east-1");
5454
private static final HubTestConfig.VaultSpec awsSTSVaultConfig = new HubTestConfig.VaultSpec("AWS STS", "844BD517-96D4-4787-BCFA-238E103149F6",
5555
null, null, null, "eu-west-1");
5656
private static final HubTestConfig.VaultSpec awsStaticVaultConfig = new HubTestConfig.VaultSpec("AWS static", "72736C19-283C-49D3-80A5-AB74B5202543",
57-
"handmade2",
58-
PROPERTIES.get("handmade2.s3.amazonaws.com.username"),
59-
PROPERTIES.get("handmade2.s3.amazonaws.com.password"),
60-
"eu-north-1"
57+
"handmade2", PROPERTIES.get("handmade2.s3.amazonaws.com.username"), PROPERTIES.get("handmade2.s3.amazonaws.com.password"), "eu-north-1"
6158
);
6259

6360
/**

0 commit comments

Comments
 (0)