Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Commit

Permalink
expose API to createOrUpdate secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
alokmenghrajani committed Jun 15, 2016
1 parent 1a39820 commit 3ccefa2
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 52 deletions.
@@ -0,0 +1,85 @@
package keywhiz.api.automation.v2;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import keywhiz.api.validation.ValidBase64;

import javax.annotation.Nullable;
import java.util.Base64;
import java.util.Map;

@AutoValue public abstract class CreateOrUpdateSecretRequestV2 {
CreateOrUpdateSecretRequestV2() {} // prevent sub-classing

public static Builder builder() {
return new AutoValue_CreateOrUpdateSecretRequestV2.Builder()
.description("")
.metadata(ImmutableMap.of())
.expiry(0)
.type("");
}

@AutoValue.Builder public abstract static class Builder {
// intended to be package-private
abstract String content();
abstract CreateOrUpdateSecretRequestV2 autoBuild();

public abstract Builder content(String content);
public abstract Builder description(String description);
public abstract Builder metadata(ImmutableMap<String, String> metadata);
public abstract Builder type(String type);
public abstract Builder expiry(long expiry);

/**
* @throws IllegalArgumentException if builder data is invalid.
*/
public CreateOrUpdateSecretRequestV2 build() {
// throws IllegalArgumentException if content not valid base64.
Base64.getDecoder().decode(content());

CreateOrUpdateSecretRequestV2 request = autoBuild();
return request;
}
}

/**
* Static factory method used by Jackson for deserialization
*/
@SuppressWarnings("unused")
@JsonCreator public static CreateOrUpdateSecretRequestV2 fromParts(
@JsonProperty("content") String content,
@JsonProperty("description") @Nullable String description,
@JsonProperty("metadata") @Nullable Map<String, String> metadata,
@JsonProperty("expiry") long expiry,
@JsonProperty("type") @Nullable String type) {
return builder()
.content(content)
.description(Strings.nullToEmpty(description))
.metadata(metadata == null ? ImmutableMap.of() : ImmutableMap.copyOf(metadata))
.expiry(expiry)
.type(Strings.nullToEmpty(type))
.build();
}

@JsonProperty("content") @ValidBase64 public abstract String content();
@JsonProperty("description") public abstract String description();
@JsonProperty("metadata") public abstract ImmutableMap<String, String> metadata();
@JsonProperty("expiry") public abstract long expiry();
@JsonProperty("type") public abstract String type();

@Override public final String toString() {
return MoreObjects.toStringHelper(this)
.add("content", "[REDACTED]")
.add("description", description())
.add("metadata", metadata())
.add("expiry", expiry())
.add("type", type())
.omitNullValues()
.toString();
}
}
11 changes: 3 additions & 8 deletions cli/src/main/java/keywhiz/cli/CliMain.java
Expand Up @@ -21,14 +21,8 @@
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import java.util.Map; import java.util.Map;
import keywhiz.cli.configs.AddActionConfig;
import keywhiz.cli.configs.AssignActionConfig; import keywhiz.cli.configs.*;
import keywhiz.cli.configs.CliConfiguration;
import keywhiz.cli.configs.DeleteActionConfig;
import keywhiz.cli.configs.DescribeActionConfig;
import keywhiz.cli.configs.ListActionConfig;
import keywhiz.cli.configs.LoginActionConfig;
import keywhiz.cli.configs.UnassignActionConfig;


/** Keywhiz ACL Command Line Management Utility */ /** Keywhiz ACL Command Line Management Utility */
public class CliMain { public class CliMain {
Expand All @@ -41,6 +35,7 @@ public static void main(String[] args) throws Exception {
.put("list", new ListActionConfig()) .put("list", new ListActionConfig())
.put("describe", new DescribeActionConfig()) .put("describe", new DescribeActionConfig())
.put("add", new AddActionConfig()) .put("add", new AddActionConfig())
.put("update", new CreateOrUpdateActionConfig())
.put("delete", new DeleteActionConfig()) .put("delete", new DeleteActionConfig())
.put("assign", new AssignActionConfig()) .put("assign", new AssignActionConfig())
.put("unassign", new UnassignActionConfig()) .put("unassign", new UnassignActionConfig())
Expand Down
24 changes: 9 additions & 15 deletions cli/src/main/java/keywhiz/cli/CommandExecutor.java
Expand Up @@ -29,19 +29,9 @@
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import keywhiz.cli.commands.AddAction;
import keywhiz.cli.commands.AssignAction; import keywhiz.cli.commands.*;
import keywhiz.cli.commands.DeleteAction; import keywhiz.cli.configs.*;
import keywhiz.cli.commands.DescribeAction;
import keywhiz.cli.commands.ListAction;
import keywhiz.cli.commands.UnassignAction;
import keywhiz.cli.configs.AddActionConfig;
import keywhiz.cli.configs.AssignActionConfig;
import keywhiz.cli.configs.CliConfiguration;
import keywhiz.cli.configs.DeleteActionConfig;
import keywhiz.cli.configs.DescribeActionConfig;
import keywhiz.cli.configs.ListActionConfig;
import keywhiz.cli.configs.UnassignActionConfig;
import keywhiz.client.KeywhizClient; import keywhiz.client.KeywhizClient;
import keywhiz.client.KeywhizClient.UnauthorizedException; import keywhiz.client.KeywhizClient.UnauthorizedException;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
Expand All @@ -52,9 +42,9 @@
import static java.lang.String.format; import static java.lang.String.format;


public class CommandExecutor { public class CommandExecutor {
public static final String APP_VERSION = "2.0"; public static final String APP_VERSION = "2.1";


public enum Command { LOGIN, LIST, DESCRIBE, ADD, DELETE, ASSIGN, UNASSIGN } public enum Command { LOGIN, LIST, DESCRIBE, ADD, UPDATE, DELETE, ASSIGN, UNASSIGN }


private final Path cookieDir = Paths.get(USER_HOME.value()); private final Path cookieDir = Paths.get(USER_HOME.value());


Expand Down Expand Up @@ -144,6 +134,10 @@ public void executeCommand() throws IOException {
new AddAction((AddActionConfig) commands.get(command), client, mapper).run(); new AddAction((AddActionConfig) commands.get(command), client, mapper).run();
break; break;


case UPDATE:
new CreateOrUpdateAction((CreateOrUpdateActionConfig) commands.get(command), client, mapper).run();
break;

case DELETE: case DELETE:
new DeleteAction((DeleteActionConfig) commands.get(command), client).run(); new DeleteAction((DeleteActionConfig) commands.get(command), client).run();
break; break;
Expand Down
128 changes: 128 additions & 0 deletions cli/src/main/java/keywhiz/cli/commands/CreateOrUpdateAction.java
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package keywhiz.cli.commands;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import keywhiz.cli.configs.CreateOrUpdateActionConfig;
import keywhiz.client.KeywhizClient;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;

import static java.lang.String.format;
import static keywhiz.cli.Utilities.VALID_NAME_PATTERN;
import static keywhiz.cli.Utilities.validName;

public class CreateOrUpdateAction implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(CreateOrUpdateAction.class);

private final CreateOrUpdateActionConfig createOrUpdateActionConfig;
private final KeywhizClient keywhizClient;
private final ObjectMapper mapper;

InputStream stream = System.in;

public CreateOrUpdateAction(CreateOrUpdateActionConfig createOrUpdateActionConfig, KeywhizClient client, ObjectMapper mapper) {
this.createOrUpdateActionConfig = createOrUpdateActionConfig;
this.keywhizClient = client;
this.mapper = mapper;
}

@Override public void run() {
String secretName = createOrUpdateActionConfig.secretName;

if (secretName == null || !validName(secretName)) {
throw new IllegalArgumentException(format("Invalid name, must match %s", VALID_NAME_PATTERN));
}

byte[] content = readSecretContent();
createOrUpdateSecret(secretName, content, getMetadata(), getExpiry());
}

private void createOrUpdateSecret(String secretName, byte[] content,
ImmutableMap<String, String> metadata, long expiry) {
try {
keywhizClient.createOrUpdateSecret(secretName, "", content, metadata, expiry);
logger.info("createOrUpdate secret '{}'.", secretName);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}

private ImmutableMap<String, String> getMetadata() {
ImmutableMap<String, String> metadata = ImmutableMap.of();
String jsonBlob = createOrUpdateActionConfig.json;
if (jsonBlob != null && !jsonBlob.isEmpty()) {
TypeReference typeRef = new TypeReference<ImmutableMap<String, String>>() {};
try {
metadata = mapper.readValue(jsonBlob, typeRef);
} catch (IOException e) {
throw Throwables.propagate(e);
}
validateMetadata(metadata);
}
return metadata;
}

private long getExpiry() {
String expiry = createOrUpdateActionConfig.expiry;
if (expiry != null) {
try {
return Long.parseLong(expiry);
} catch (NumberFormatException e) {
}
DateTime dt = new DateTime(expiry);
return dt.getMillis();
}
return 0;
}

private byte[] readSecretContent() {
try {
byte[] content = ByteStreams.toByteArray(stream);
if (content.length == 0) {
throw new RuntimeException("Secret content empty!");
}
return content;
} catch (IOException e) {
throw Throwables.propagate(e);
}
}

private static void validateMetadata(ImmutableMap<String, String> metadata) {
for (ImmutableMap.Entry<String, String> entry : metadata.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();

// We want to perform strong validation of the metadata to make sure it is well formed.
if (!key.matches("(owner|group|mode)")) {
throw new IllegalArgumentException(format("Illegal metadata key %s", key));
}

if (key.equals("mode") && !value.matches("0[0-7]+")) {
throw new IllegalArgumentException(format("mode %s is not proper octal", value));
}
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package keywhiz.cli.configs;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;

import java.util.List;

@Parameters(commandDescription = "Create or update secret.")
public class CreateOrUpdateActionConfig {
@Parameter(names = "--secret", description = "Name of the secret to add", required = true)
public String secretName;

@Parameter(names = "--json", description = "Metadata JSON blob")
public String json;

@Parameter(names = { "-e", "--expiry" }, description = "Secret expiry. For keystores, it is recommended to use the expiry of the earliest key. Format should be 2006-01-02T15:04:05Z or seconds since epoch.")
public String expiry;
}
17 changes: 17 additions & 0 deletions client/src/main/java/keywhiz/client/KeywhizClient.java
Expand Up @@ -30,6 +30,7 @@
import keywhiz.api.GroupDetailResponse; import keywhiz.api.GroupDetailResponse;
import keywhiz.api.LoginRequest; import keywhiz.api.LoginRequest;
import keywhiz.api.SecretDetailResponse; import keywhiz.api.SecretDetailResponse;
import keywhiz.api.automation.v2.CreateOrUpdateSecretRequestV2;
import keywhiz.api.model.Client; import keywhiz.api.model.Client;
import keywhiz.api.model.Group; import keywhiz.api.model.Group;
import keywhiz.api.model.SanitizedSecret; import keywhiz.api.model.SanitizedSecret;
Expand Down Expand Up @@ -155,6 +156,22 @@ public SecretDetailResponse createSecret(String name, String description, byte[]
return mapper.readValue(response, SecretDetailResponse.class); return mapper.readValue(response, SecretDetailResponse.class);
} }


public SecretDetailResponse createOrUpdateSecret(String name, String description, byte[] content,
ImmutableMap<String, String> metadata, long expiry) throws IOException {
checkArgument(!name.isEmpty());
checkArgument(content.length > 0, "Content must not be empty");

String b64Content = Base64.getEncoder().encodeToString(content);
CreateOrUpdateSecretRequestV2 request = CreateOrUpdateSecretRequestV2.builder()
.description(description)
.content(b64Content)
.metadata(metadata)
.expiry(expiry)
.build();
String response = httpPost(baseUrl.resolve(format("/admin/secrets/%s", name)), request);
return mapper.readValue(response, SecretDetailResponse.class);
}

public SecretDetailResponse secretDetailsForId(long secretId) throws IOException { public SecretDetailResponse secretDetailsForId(long secretId) throws IOException {
String response = httpGet(baseUrl.resolve(format("/admin/secrets/%d", secretId))); String response = httpGet(baseUrl.resolve(format("/admin/secrets/%d", secretId)));
return mapper.readValue(response, SecretDetailResponse.class); return mapper.readValue(response, SecretDetailResponse.class);
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/java/keywhiz/KeywhizConfig.java
Expand Up @@ -179,7 +179,7 @@ public static class TemplatedDataSourceFactory extends DataSourceFactory {
} }
} }


// Sets the evaluated password before calling the parent's build method. // Sets the evaluated password before calling the parent's create method.
@Override public ManagedDataSource build(MetricRegistry metricRegistry, String name) { @Override public ManagedDataSource build(MetricRegistry metricRegistry, String name) {
setPassword(getPassword()); setPassword(getPassword());
return super.build(metricRegistry, name); return super.build(metricRegistry, name);
Expand Down
Expand Up @@ -145,10 +145,16 @@ public SecretBuilder withType(String type) {
* *
* @return an instance of the newly created secret. * @return an instance of the newly created secret.
*/ */
public Secret build() { public Secret create() {
secretDAO.createSecret(name, encryptedSecret, creator, metadata, expiry, description, type, secretDAO.createSecret(name, encryptedSecret, creator, metadata, expiry, description, type,
generationOptions); generationOptions);
return transformer.transform(secretDAO.getSecretByNameOne(name).get()); return transformer.transform(secretDAO.getSecretByNameOne(name).get());
} }

public Secret createOrUpdate() {
secretDAO.createOrUpdateSecret(name, encryptedSecret, creator, metadata, expiry, description, type,
generationOptions);
return transformer.transform(secretDAO.getSecretByNameOne(name).get());
}
} }
} }

0 comments on commit 3ccefa2

Please sign in to comment.