Skip to content

Commit

Permalink
[Extensions] Generate auth tokens for service accounts (opensearch-pr…
Browse files Browse the repository at this point in the history
…oject#2716)

* Generate auth tokens for service accounts

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com>
  • Loading branch information
stephen-crawford committed May 3, 2023
1 parent 7f48001 commit 5cd57ae
Show file tree
Hide file tree
Showing 8 changed files with 610 additions and 53 deletions.
58 changes: 32 additions & 26 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,16 @@ licenseFile = rootProject.file('LICENSE.txt')
noticeFile = rootProject.file('NOTICE.txt')

spotless {
java {
// note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports
importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#')
}
java {
// note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports
importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#')
targetExclude('src/integrationTest/**')
}
format("integrationTest", JavaExtension) {
target('src/integrationTest/java/**/*.java')
importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#')
indentWithTabs(4)
}
}

spotbugs {
Expand Down Expand Up @@ -120,17 +126,17 @@ test {
}
jacoco {
excludes = [
"com.sun.jndi.dns.*",
"com.sun.security.sasl.gsskerb.*",
"java.sql.*",
"javax.script.*",
"org.jcp.xml.dsig.internal.dom.*",
"sun.nio.cs.ext.*",
"sun.security.ec.*",
"sun.security.jgss.*",
"sun.security.pkcs11.*",
"sun.security.smartcardio.*",
"sun.util.resources.provider.*"
"com.sun.jndi.dns.*",
"com.sun.security.sasl.gsskerb.*",
"java.sql.*",
"javax.script.*",
"org.jcp.xml.dsig.internal.dom.*",
"sun.nio.cs.ext.*",
"sun.security.ec.*",
"sun.security.jgss.*",
"sun.security.pkcs11.*",
"sun.security.smartcardio.*",
"sun.util.resources.provider.*"
]
}
}
Expand All @@ -144,17 +150,17 @@ task opensslTest(type: Test) {
}
jacoco {
excludes = [
"com.sun.jndi.dns.*",
"com.sun.security.sasl.gsskerb.*",
"java.sql.*",
"javax.script.*",
"org.jcp.xml.dsig.internal.dom.*",
"sun.nio.cs.ext.*",
"sun.security.ec.*",
"sun.security.jgss.*",
"sun.security.pkcs11.*",
"sun.security.smartcardio.*",
"sun.util.resources.provider.*"
"com.sun.jndi.dns.*",
"com.sun.security.sasl.gsskerb.*",
"java.sql.*",
"javax.script.*",
"org.jcp.xml.dsig.internal.dom.*",
"sun.nio.cs.ext.*",
"sun.security.ec.*",
"sun.security.jgss.*",
"sun.security.pkcs11.*",
"sun.security.smartcardio.*",
"sun.util.resources.provider.*"
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ public List<Path> run() {
final List<String> files = AccessController.doPrivileged(new PrivilegedAction<List<String>>() {
@Override
public List<String> run() {

final Path confPath = new Environment(settings, configPath).configFile().toAbsolutePath();

if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) {
try (Stream<Path> s = Files.walk(confPath)) {
return s.distinct().map(p -> sha256(p)).collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,20 @@

public class InternalUsersApiAction extends PatchableResourceApiAction {
static final List<String> RESTRICTED_FROM_USERNAME = ImmutableList.of(
":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057
":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057
);

private static final List<Route> routes = addRoutesPrefix(ImmutableList.of(
new Route(Method.GET, "/user/{name}"),
new Route(Method.GET, "/user/"),
new Route(Method.POST, "/user/{name}/authtoken"),
new Route(Method.DELETE, "/user/{name}"),
new Route(Method.PUT, "/user/{name}"),

// corrected mapping, introduced in OpenSearch Security
new Route(Method.GET, "/internalusers/{name}"),
new Route(Method.GET, "/internalusers/"),
new Route(Method.POST, "/internalusers/{name}/authtoken"),
new Route(Method.DELETE, "/internalusers/{name}"),
new Route(Method.PUT, "/internalusers/{name}"),
new Route(Method.PATCH, "/internalusers/"),
Expand Down Expand Up @@ -142,9 +144,19 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C
// when updating an existing user password hash can be blank, which means no
// changes

// sanity checks, hash is mandatory for newly created users
if (!userExisted && securityJsonNode.get("hash").asString() == null) {
badRequestResponse(channel, "Please specify either 'hash' or 'password' when creating a new internal user.");
try {
if (request.hasParam("service")) {
((ObjectNode) content).put("service", request.param("service"));
}
if (request.hasParam("enabled")) {
((ObjectNode) content).put("enabled", request.param("enabled"));
}
((ObjectNode) content).put("name", username);
internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content);
}
catch (UserServiceException ex) {
badRequestResponse(channel, ex.getMessage());

return;
}

Expand All @@ -160,13 +172,28 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C
contentAsNode.put("hash", hash);
}

// for existing users, hash is optional
if (userExisted && securityJsonNode.get("hash").asString() == null) {
// sanity check, this should usually not happen
final String hash = ((Hashed) internalUsersConfiguration.getCEntry(username)).getHash();
if (hash == null || hash.length() == 0) {
internalErrorResponse(channel,
"Existing user " + username + " has no password, and no new password or hash was specified.");
return;
}
contentAsNode.put("hash", hash);
}

internalUsersConfiguration.remove(username);

// checks complete, create or update the user
internalUsersConfiguration.putCObject(username, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass()));
Object userData = DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass());
internalUsersConfiguration.putCObject(username, userData);


saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener<IndexResponse>(channel) {


@Override
public void onResponse(IndexResponse response) {
if (userExisted) {
Expand All @@ -179,6 +206,63 @@ public void onResponse(IndexResponse response) {
});
}

/**
* Overrides the GET request functionality to allow for the special case of requesting an auth token.
*
* @param channel The channel the request is coming through
* @param request The request itself
* @param client The client executing the request
* @param content The content of the request parsed into a node
* @throws IOException when parsing of configuration files fails (should not happen)
*/
@Override
protected void handlePost(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{

final String username = request.param("name");

final SecurityDynamicConfiguration<?> internalUsersConfiguration = load(getConfigName(), true);
filter(internalUsersConfiguration); // Hides hashes

// no specific resource requested
if (username == null || username.length() == 0) {

notImplemented(channel, Method.POST);
return;
}

final boolean userExisted = internalUsersConfiguration.exists(username);

if (!userExisted) {
notFound(channel, "Resource '" + username + "' not found.");
return;
}

String authToken = "";
try {
if (request.uri().contains("/internalusers/" + username + "/authtoken") && request.uri().endsWith("/authtoken")) { // Handle auth token fetching

authToken = userService.generateAuthToken(username);
} else { // Not an auth token request

notImplemented(channel, Method.POST);
return;
}
} catch (UserServiceException ex) {
badRequestResponse(channel, ex.getMessage());
return;
}
catch (IOException ex) {
throw new IOException(ex);
}

if (!authToken.isEmpty()) {
createdResponse(channel, "'" + username + "' authtoken generated " + authToken);
} else {
badRequestResponse(channel, "'" + username + "' authtoken failed to be created.");
}
}


@Override
protected void filter(SecurityDynamicConfiguration<?> builder) {
super.filter(builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class InternalUserV7 implements Hideable, Hashed, StaticDefinable {
private String hash;
private boolean reserved;
private boolean hidden;
private boolean service;
private boolean enabled;
@JsonProperty(value = "static")
private boolean _static;
private List<String> backend_roles = Collections.emptyList();
Expand All @@ -58,7 +60,20 @@ private InternalUserV7(String hash, boolean reserved, boolean hidden, List<Strin
this.hidden = hidden;
this.backend_roles = backend_roles;
this.attributes = attributes;
}
this.enabled = true;
this.service = false;
}

private InternalUserV7(String hash, boolean reserved, boolean hidden, List<String> backend_roles, Map<String, String> attributes, Boolean enabled, Boolean service) {
super();
this.hash = hash;
this.reserved = reserved;
this.hidden = hidden;
this.backend_roles = backend_roles;
this.attributes = attributes;
this.enabled = enabled;
this.service = service;
}

public InternalUserV7() {
super();
Expand All @@ -80,7 +95,6 @@ public String getHash() {
public void setHash(String hash) {
this.hash = hash;
}



public boolean isHidden() {
Expand Down Expand Up @@ -114,9 +128,17 @@ public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}

public boolean enabled() {
return this.enabled;
}

public boolean service() {
return this.service;
}

@Override
public String toString() {
return "InternalUserV7 [hash=" + hash + ", reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", backend_roles="
return "InternalUserV7 [hash=" + hash + ", enabled=" + enabled + ", service=" + service + ", reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", backend_roles="
+ backend_roles + ", attributes=" + attributes + ", description=" + description + "]";
}

Expand All @@ -134,6 +156,14 @@ public void setDescription(String description) {
this.description = description;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public void setService(boolean service) {
this.service = service;
}

public boolean isReserved() {
return reserved;
}
Expand Down
Loading

0 comments on commit 5cd57ae

Please sign in to comment.