Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Accounts - Get service account API #71315

Merged
merged 5 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.service;

import org.elasticsearch.action.ActionType;

public class GetServiceAccountAction extends ActionType<GetServiceAccountResponse> {

public static final String NAME = "cluster:admin/xpack/security/service_account/get";
public static final GetServiceAccountAction INSTANCE = new GetServiceAccountAction();

public GetServiceAccountAction() {
super(NAME, GetServiceAccountResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.service;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;
import java.util.Objects;

public class GetServiceAccountRequest extends ActionRequest {

@Nullable
private final String namespace;
@Nullable
private final String serviceName;

public GetServiceAccountRequest(@Nullable String namespace, @Nullable String serviceName) {
this.namespace = namespace;
this.serviceName = serviceName;
}

public GetServiceAccountRequest(StreamInput in) throws IOException {
super(in);
this.namespace = in.readOptionalString();
this.serviceName = in.readOptionalString();
}

public String getNamespace() {
return namespace;
}

public String getServiceName() {
return serviceName;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
GetServiceAccountRequest that = (GetServiceAccountRequest) o;
return Objects.equals(namespace, that.namespace) && Objects.equals(serviceName, that.serviceName);
}

@Override
public int hashCode() {
return Objects.hash(namespace, serviceName);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalString(namespace);
out.writeOptionalString(serviceName);
}

@Override
public ActionRequestValidationException validate() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.service;

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

public class GetServiceAccountResponse extends ActionResponse implements ToXContentObject {

private final ServiceAccountInfo[] serviceAccountInfos;

public GetServiceAccountResponse(ServiceAccountInfo[] serviceAccountInfos) {
this.serviceAccountInfos = Objects.requireNonNull(serviceAccountInfos);
}

public GetServiceAccountResponse(StreamInput in) throws IOException {
super(in);
this.serviceAccountInfos = in.readArray(ServiceAccountInfo::new, ServiceAccountInfo[]::new);
}

public ServiceAccountInfo[] getServiceAccountInfos() {
return serviceAccountInfos;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeArray(serviceAccountInfos);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
for (ServiceAccountInfo info : serviceAccountInfos) {
info.toXContent(builder, params);
}
builder.endObject();
return builder;
}

@Override
public String toString() {
return "GetServiceAccountResponse{" + "serviceAccountInfos=" + Arrays.toString(serviceAccountInfos) + '}';
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
GetServiceAccountResponse that = (GetServiceAccountResponse) o;
return Arrays.equals(serviceAccountInfos, that.serviceAccountInfos);
}

@Override
public int hashCode() {
return Arrays.hashCode(serviceAccountInfos);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.service;

import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;

import java.io.IOException;
import java.util.Objects;

public class ServiceAccountInfo implements Writeable, ToXContent {

private final String principal;
private final RoleDescriptor roleDescriptor;

public ServiceAccountInfo(String principal, RoleDescriptor roleDescriptor) {
this.principal = Objects.requireNonNull(principal, "service account principal cannot be null");
this.roleDescriptor = Objects.requireNonNull(roleDescriptor, "service account descriptor cannot be null");
}

public ServiceAccountInfo(StreamInput in) throws IOException {
this.principal = in.readString();
this.roleDescriptor = new RoleDescriptor(in);
}

public String getPrincipal() {
return principal;
}

public RoleDescriptor getRoleDescriptor() {
return roleDescriptor;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(principal);
roleDescriptor.writeTo(out);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(principal);
builder.field("role_descriptor");
roleDescriptor.toXContent(builder, params);
builder.endObject();
return builder;
}

@Override
public String toString() {
return "ServiceAccountInfo{" + "principal='" + principal + '\'' + ", roleDescriptor=" + roleDescriptor + '}';
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ServiceAccountInfo that = (ServiceAccountInfo) o;
return principal.equals(that.principal) && roleDescriptor.equals(that.roleDescriptor);
}

@Override
public int hashCode() {
return Objects.hash(principal, roleDescriptor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.service;

import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.test.AbstractWireSerializingTestCase;

import java.io.IOException;

public class GetServiceAccountRequestTests extends AbstractWireSerializingTestCase<GetServiceAccountRequest> {

@Override
protected Writeable.Reader<GetServiceAccountRequest> instanceReader() {
return GetServiceAccountRequest::new;
}

@Override
protected GetServiceAccountRequest createTestInstance() {
return new GetServiceAccountRequest(randomFrom(randomAlphaOfLengthBetween(3, 8), null),
randomFrom(randomAlphaOfLengthBetween(3, 8), null));
}

@Override
protected GetServiceAccountRequest mutateInstance(GetServiceAccountRequest instance) throws IOException {
if (randomBoolean()) {
return new GetServiceAccountRequest(
randomValueOtherThan(instance.getNamespace(), () -> randomFrom(randomAlphaOfLengthBetween(3, 8), null)),
instance.getServiceName());
} else {
return new GetServiceAccountRequest(
instance.getNamespace(),
randomValueOtherThan(instance.getServiceName(), () -> randomFrom(randomAlphaOfLengthBetween(3, 8), null)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.service;

import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.test.XContentTestUtils;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;

import java.io.IOException;
import java.util.Map;

import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.equalTo;

public class GetServiceAccountResponseTests extends AbstractWireSerializingTestCase<GetServiceAccountResponse> {

@Override
protected Writeable.Reader<GetServiceAccountResponse> instanceReader() {
return GetServiceAccountResponse::new;
}

@Override
protected GetServiceAccountResponse createTestInstance() {
final String principal = randomPrincipal();
return new GetServiceAccountResponse(randomBoolean()
? new ServiceAccountInfo[]{new ServiceAccountInfo(principal, getRoleDescriptorFor(principal))}
: new ServiceAccountInfo[0]);
}

@Override
protected GetServiceAccountResponse mutateInstance(GetServiceAccountResponse instance) throws IOException {
if (instance.getServiceAccountInfos().length == 0) {
final String principal = randomPrincipal();
return new GetServiceAccountResponse(new ServiceAccountInfo[]{
new ServiceAccountInfo(principal, getRoleDescriptorFor(principal))});
} else {
return new GetServiceAccountResponse(new ServiceAccountInfo[0]);
}
}

@SuppressWarnings("unchecked")
public void testToXContent() throws IOException {
final GetServiceAccountResponse response = createTestInstance();
XContentBuilder builder = XContentFactory.jsonBuilder();
response.toXContent(builder, ToXContent.EMPTY_PARAMS);
final Map<String, Object> responseMap = XContentHelper.convertToMap(
BytesReference.bytes(builder),
false, builder.contentType()).v2();
final ServiceAccountInfo[] serviceAccountInfos = response.getServiceAccountInfos();
if (serviceAccountInfos.length == 0) {
assertThat(responseMap, anEmptyMap());
} else {
assertThat(responseMap.size(), equalTo(serviceAccountInfos.length));
for (int i = 0; i < serviceAccountInfos.length - 1; i++) {
final String key = serviceAccountInfos[i].getPrincipal();
assertRoleDescriptorEquals((Map<String, Object>) responseMap.get(key), serviceAccountInfos[i].getRoleDescriptor());
}
}
}

private String randomPrincipal() {
return randomAlphaOfLengthBetween(3, 8) + "/" + randomAlphaOfLengthBetween(3, 8);
}

private RoleDescriptor getRoleDescriptorFor(String name) {
return new RoleDescriptor(name,
new String[] { "monitor", "manage_own_api_key" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.indices("logs-*", "metrics-*", "traces-*")
.privileges("write", "create_index", "auto_configure").build() },
null,
null,
null,
null,
null);
}

private void assertRoleDescriptorEquals(Map<String, Object> responseFragment, RoleDescriptor roleDescriptor) throws IOException {
@SuppressWarnings("unchecked")
final Map<String, Object> descriptorMap = (Map<String, Object>) responseFragment.get("role_descriptor");
assertThat(RoleDescriptor.parse(roleDescriptor.getName(),
XContentTestUtils.convertToXContent(descriptorMap, XContentType.JSON), false, XContentType.JSON),
equalTo(roleDescriptor));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public class Constants {
"cluster:admin/xpack/security/saml/invalidate",
"cluster:admin/xpack/security/saml/logout",
"cluster:admin/xpack/security/saml/prepare",
"cluster:admin/xpack/security/service_account/get",
"cluster:admin/xpack/security/service_account/token/create",
"cluster:admin/xpack/security/service_account/token/get",
"cluster:admin/xpack/security/token/create",
Expand Down
Loading