package io.fathom.cloud.secrets.services;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.keyczar.KeyczarFactory;
import io.fathom.cloud.protobuf.SecretsModel.SecretRecordData;
import io.fathom.cloud.protobuf.SecretsModel.SecretRecordItemData;
import io.fathom.cloud.server.auth.Auth;
import io.fathom.cloud.server.model.Project;
import io.fathom.cloud.services.Attachments;
import io.fathom.cloud.services.AuthService;
import io.fathom.cloud.services.SecretService;
import io.fathom.cloud.services.Attachments.ClientApp;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.keyczar.AesKey;
import org.keyczar.Crypter;
import org.keyczar.KeyczarUtils;
import org.keyczar.exceptions.KeyczarException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fathomdb.Configuration;
import com.google.common.collect.Lists;
import com.google.inject.persist.Transactional;
import com.google.protobuf.ByteString;
@Singleton
@Transactional
public class SecretServiceImpl implements SecretService {
private static final Logger log = LoggerFactory.getLogger(SecretServiceImpl.class);
@Inject
KeyczarFactory keyczarFactory;
@Inject
Attachments attachments;
@Inject
AuthService authService;
@Inject
SecretRepository repository;
@Inject
Configuration config;
ClientApp clientApp;
ClientApp getClientApp() throws CloudException {
if (clientApp == null) {
Project project = authService.findSystemProject();
if (project == null) {
throw new IllegalStateException("Cannot find system project");
}
String appName = config.lookup("secrets.appname", "org.openstack::secrets");
String appSecret = config.find("secrets.appsecret");
if (appSecret == null) {
// We rely on the project secret...
log.warn("Using default app-secret");
appSecret = "notasecret";
}
this.clientApp = attachments.findClientAppByName(project, appName, appSecret);
if (clientApp == null) {
throw new IllegalStateException("Secret store not configured as an application");
}
}
return clientApp;
}
@Override
public List<Secret> list(Auth auth, Project project) throws CloudException {
Crypter crypter = getSecret(auth, project);
List<Secret> secrets = Lists.newArrayList();
for (SecretRecordData data : repository.getSecrets(project).list()) {
secrets.add(new SecretImpl(project, data, crypter));
}
return secrets;
}
private Crypter getSecret(Auth auth, Project project) throws CloudException {
byte[] secret = attachments.findProjectSecret(getClientApp(), auth, project);
AesKey key;
if (secret == null) {
key = KeyczarUtils.generateSymmetricKey();
secret = KeyczarUtils.pack(key);
attachments.setProjectSecret(getClientApp(), auth, project, secret);
} else {
try {
key = KeyczarUtils.unpack(secret);
} catch (KeyczarException e) {
throw new CloudException("Error unpacking key", e);
}
}
return KeyczarUtils.buildCrypter(key);
}
@Override
public Secret find(Auth auth, Project project, long id) throws CloudException {
Crypter crypter = getSecret(auth, project);
SecretRecordData data = repository.getSecrets(project).find(id);
if (data == null) {
return null;
}
return new SecretImpl(project, data, crypter);
}
@Override
public Secret deleteKey(Auth auth, Project project, long id) throws CloudException {
Crypter crypter = getSecret(auth, project);
SecretRecordData data = repository.getSecrets(project).delete(id);
if (data == null) {
return null;
}
return new SecretImpl(project, data, crypter);
}
@Override
public Secret setSecretItem(Auth auth, Secret secret, String key, byte[] data) throws CloudException {
SecretImpl secretImpl = (SecretImpl) secret;
secretImpl = (SecretImpl) find(auth, secretImpl.getProject(), secretImpl.getData().getId());
SecretRecordData secretData = secretImpl.getData();
SecretRecordData.Builder b = SecretRecordData.newBuilder(secretData);
SecretRecordItemData.Builder item = null;
for (SecretRecordItemData.Builder i : b.getItemBuilderList()) {
if (i.getKey().equals(key)) {
item = i;
break;
}
}
if (item == null) {
item = b.addItemBuilder();
item.setKey(key);
}
Crypter crypter = secretImpl.getCrypter();
byte[] ciphertext;
try {
ciphertext = crypter.encrypt(data);
} catch (KeyczarException e) {
throw new IllegalStateException("Error encrypting secret", e);
}
item.setCiphertext(ByteString.copyFrom(ciphertext));
Project project = secretImpl.getProject();
secretData = repository.getSecrets(project).update(b);
return new SecretImpl(project, secretData, crypter);
}
@Override
public Secret create(Auth auth, Project project, SecretInfo secretInfo) throws CloudException {
Crypter crypter = getSecret(auth, project);
SecretRecordData.Builder b = SecretRecordData.newBuilder();
if (secretInfo.name != null) {
b.setName(secretInfo.name);
}
if (secretInfo.algorithm != null) {
b.setAlgorithm(secretInfo.algorithm);
}
if (secretInfo.keySize != 0) {
b.setKeySize(secretInfo.keySize);
}
if (secretInfo.subject != null) {
b.setSubject(secretInfo.subject);
}
SecretRecordData secretData = repository.getSecrets(project).create(b);
return new SecretImpl(project, secretData, crypter);
}
}