Skip to content

Commit

Permalink
allow authenticated users to request certs for services (#309)
Browse files Browse the repository at this point in the history
  • Loading branch information
havetisyan authored Nov 1, 2017
1 parent dc2ec63 commit 145d19c
Show file tree
Hide file tree
Showing 22 changed files with 1,212 additions and 463 deletions.
133 changes: 5 additions & 128 deletions clients/go/zts/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -1304,134 +1304,6 @@ func (self *Identity) Validate() error {
return nil
}

//
// InstanceInformation - Instance object that includes requested service
// details plus host document that is signed by provider as part of the host
// bootstrap process
//
type InstanceInformation struct {

//
// signed document containing attributes like IP address, instance-id,
// account#, etc.
//
Document string `json:"document"`

//
// the signature for the document
//
Signature string `json:"signature"`

//
// the keyid used to sign the document
//
KeyId string `json:"keyId"`

//
// the domain of the instance
//
Domain CompoundName `json:"domain"`

//
// the service this instance is supposed to run
//
Service SimpleName `json:"service"`

//
// return a certificate in the response
//
Csr string `json:"csr"`

//
// if present, return an SSH host certificate
//
Ssh string `json:"ssh,omitempty" rdl:"optional"`
}

//
// NewInstanceInformation - creates an initialized InstanceInformation instance, returns a pointer to it
//
func NewInstanceInformation(init ...*InstanceInformation) *InstanceInformation {
var o *InstanceInformation
if len(init) == 1 {
o = init[0]
} else {
o = new(InstanceInformation)
}
return o
}

type rawInstanceInformation InstanceInformation

//
// UnmarshalJSON is defined for proper JSON decoding of a InstanceInformation
//
func (self *InstanceInformation) UnmarshalJSON(b []byte) error {
var m rawInstanceInformation
err := json.Unmarshal(b, &m)
if err == nil {
o := InstanceInformation(m)
*self = o
err = self.Validate()
}
return err
}

//
// Validate - checks for missing required fields, etc
//
func (self *InstanceInformation) Validate() error {
if self.Document == "" {
return fmt.Errorf("InstanceInformation.document is missing but is a required field")
} else {
val := rdl.Validate(ZTSSchema(), "String", self.Document)
if !val.Valid {
return fmt.Errorf("InstanceInformation.document does not contain a valid String (%v)", val.Error)
}
}
if self.Signature == "" {
return fmt.Errorf("InstanceInformation.signature is missing but is a required field")
} else {
val := rdl.Validate(ZTSSchema(), "String", self.Signature)
if !val.Valid {
return fmt.Errorf("InstanceInformation.signature does not contain a valid String (%v)", val.Error)
}
}
if self.KeyId == "" {
return fmt.Errorf("InstanceInformation.keyId is missing but is a required field")
} else {
val := rdl.Validate(ZTSSchema(), "String", self.KeyId)
if !val.Valid {
return fmt.Errorf("InstanceInformation.keyId does not contain a valid String (%v)", val.Error)
}
}
if self.Domain == "" {
return fmt.Errorf("InstanceInformation.domain is missing but is a required field")
} else {
val := rdl.Validate(ZTSSchema(), "CompoundName", self.Domain)
if !val.Valid {
return fmt.Errorf("InstanceInformation.domain does not contain a valid CompoundName (%v)", val.Error)
}
}
if self.Service == "" {
return fmt.Errorf("InstanceInformation.service is missing but is a required field")
} else {
val := rdl.Validate(ZTSSchema(), "SimpleName", self.Service)
if !val.Valid {
return fmt.Errorf("InstanceInformation.service does not contain a valid SimpleName (%v)", val.Error)
}
}
if self.Csr == "" {
return fmt.Errorf("InstanceInformation.csr is missing but is a required field")
} else {
val := rdl.Validate(ZTSSchema(), "String", self.Csr)
if !val.Valid {
return fmt.Errorf("InstanceInformation.csr does not contain a valid String (%v)", val.Error)
}
}
return nil
}

//
// InstanceRefreshRequest - InstanceRefreshRequest - a certificate refresh
// request
Expand All @@ -1447,6 +1319,11 @@ type InstanceRefreshRequest struct {
// in seconds how long token should be valid for
//
ExpiryTime *int32 `json:"expiryTime,omitempty" rdl:"optional"`

//
// public key identifier
//
KeyId string `json:"keyId,omitempty" rdl:"optional"`
}

//
Expand Down
14 changes: 2 additions & 12 deletions clients/go/zts/zts_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,21 +194,11 @@ func init() {
tIdentity.MapField("attributes", "String", "String", true, "other config-like attributes determined at boot time")
sb.AddType(tIdentity.Build())

tInstanceInformation := rdl.NewStructTypeBuilder("Struct", "InstanceInformation")
tInstanceInformation.Comment("Instance object that includes requested service details plus host document that is signed by provider as part of the host bootstrap process")
tInstanceInformation.Field("document", "String", false, nil, "signed document containing attributes like IP address, instance-id, account#, etc.")
tInstanceInformation.Field("signature", "String", false, nil, "the signature for the document")
tInstanceInformation.Field("keyId", "String", false, nil, "the keyid used to sign the document")
tInstanceInformation.Field("domain", "CompoundName", false, nil, "the domain of the instance")
tInstanceInformation.Field("service", "SimpleName", false, nil, "the service this instance is supposed to run")
tInstanceInformation.Field("csr", "String", false, nil, "return a certificate in the response")
tInstanceInformation.Field("ssh", "String", true, nil, "if present, return an SSH host certificate")
sb.AddType(tInstanceInformation.Build())

tInstanceRefreshRequest := rdl.NewStructTypeBuilder("Struct", "InstanceRefreshRequest")
tInstanceRefreshRequest.Comment("InstanceRefreshRequest - a certificate refresh request")
tInstanceRefreshRequest.Field("csr", "String", false, nil, "Cert CSR signed by the service's private key (public key registered in ZMS)")
tInstanceRefreshRequest.Field("expiryTime", "Int32", true, nil, "in seconds how long token should be valid for")
tInstanceRefreshRequest.Field("keyId", "String", true, nil, "public key identifier")
sb.AddType(tInstanceRefreshRequest.Build())

tAWSTemporaryCredentials := rdl.NewStructTypeBuilder("Struct", "AWSTemporaryCredentials")
Expand Down Expand Up @@ -431,7 +421,7 @@ func init() {
sb.AddResource(mGetTenantDomains.Build())

mPostInstanceRefreshRequest := rdl.NewResourceBuilder("Identity", "POST", "/instance/{domain}/{service}/refresh")
mPostInstanceRefreshRequest.Comment("Refresh Service NToken into TLS Certificate")
mPostInstanceRefreshRequest.Comment("Refresh Service tokens into TLS Certificate")
mPostInstanceRefreshRequest.Input("domain", "CompoundName", true, "", "", false, nil, "name of the domain requesting the refresh")
mPostInstanceRefreshRequest.Input("service", "SimpleName", true, "", "", false, nil, "name of the service requesting the refresh")
mPostInstanceRefreshRequest.Input("req", "InstanceRefreshRequest", false, "", "", false, nil, "the refresh request")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class InstanceRefreshRequest {
@RdlOptional
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Integer expiryTime;
@RdlOptional
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String keyId;

public InstanceRefreshRequest setCsr(String csr) {
this.csr = csr;
Expand All @@ -30,6 +33,13 @@ public InstanceRefreshRequest setExpiryTime(Integer expiryTime) {
public Integer getExpiryTime() {
return expiryTime;
}
public InstanceRefreshRequest setKeyId(String keyId) {
this.keyId = keyId;
return this;
}
public String getKeyId() {
return keyId;
}

@Override
public boolean equals(Object another) {
Expand All @@ -44,6 +54,9 @@ public boolean equals(Object another) {
if (expiryTime == null ? a.expiryTime != null : !expiryTime.equals(a.expiryTime)) {
return false;
}
if (keyId == null ? a.keyId != null : !keyId.equals(a.keyId)) {
return false;
}
}
return true;
}
Expand Down
15 changes: 3 additions & 12 deletions core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,11 @@ private static Schema build() {
.field("serviceToken", "SignedToken", true, "service token instead of TLS certificate")
.mapField("attributes", "String", "String", true, "other config-like attributes determined at boot time");

sb.structType("InstanceInformation")
.comment("Instance object that includes requested service details plus host document that is signed by provider as part of the host bootstrap process")
.field("document", "String", false, "signed document containing attributes like IP address, instance-id, account#, etc.")
.field("signature", "String", false, "the signature for the document")
.field("keyId", "String", false, "the keyid used to sign the document")
.field("domain", "CompoundName", false, "the domain of the instance")
.field("service", "SimpleName", false, "the service this instance is supposed to run")
.field("csr", "String", false, "return a certificate in the response")
.field("ssh", "String", true, "if present, return an SSH host certificate");

sb.structType("InstanceRefreshRequest")
.comment("InstanceRefreshRequest - a certificate refresh request")
.field("csr", "String", false, "Cert CSR signed by the service's private key (public key registered in ZMS)")
.field("expiryTime", "Int32", true, "in seconds how long token should be valid for");
.field("expiryTime", "Int32", true, "in seconds how long token should be valid for")
.field("keyId", "String", true, "public key identifier");

sb.structType("AWSTemporaryCredentials")
.field("accessKeyId", "String", false, "")
Expand Down Expand Up @@ -429,7 +420,7 @@ private static Schema build() {
;

sb.resource("InstanceRefreshRequest", "POST", "/instance/{domain}/{service}/refresh")
.comment("Refresh Service NToken into TLS Certificate")
.comment("Refresh Service tokens into TLS Certificate")
.pathParam("domain", "CompoundName", "name of the domain requesting the refresh")
.pathParam("service", "SimpleName", "name of the service requesting the refresh")
.input("req", "InstanceRefreshRequest", "the refresh request")
Expand Down
15 changes: 2 additions & 13 deletions core/zts/src/main/rdl/Identity.rdli
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,14 @@
include "Names.tdl";
include "Identity.tdl";

// Instance object that includes requested service details plus host document
// that is signed by provider as part of the host bootstrap process
type InstanceInformation Struct {
String document; //signed document containing attributes like IP address, instance-id, account#, etc.
String signature; //the signature for the document
String keyId; // the keyid used to sign the document
CompoundName domain; //the domain of the instance
SimpleName service; //the service this instance is supposed to run
String csr; //return a certificate in the response
String ssh (optional); //if present, return an SSH host certificate
}

// InstanceRefreshRequest - a certificate refresh request
type InstanceRefreshRequest Struct {
String csr; //Cert CSR signed by the service's private key (public key registered in ZMS)
Int32 expiryTime (optional); //in seconds how long token should be valid for
String keyId (optional); //public key identifier
}

// Refresh Service NToken into TLS Certificate
// Refresh Service tokens into TLS Certificate
resource Identity POST "/instance/{domain}/{service}/refresh" {
CompoundName domain; //name of the domain requesting the refresh
SimpleName service; //name of the service requesting the refresh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ public void testInstanceRefreshRequest() {
// set
i.setCsr("test_csr");
i.setExpiryTime(123456789);
i.setKeyId("v0");
i2.setCsr("test_csr");

// getter assertion
assertEquals(i.getCsr(), "test_csr");
assertEquals(i.getExpiryTime(), (Integer) 123456789);
assertEquals(i.getKeyId(), "v0");

assertTrue(i.equals(i));
assertFalse(i.equals(i2));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ public static List<String> extractX509CSRDnsNames(PKCS10CertificationRequest cer
return dnsNames;
}

public static List<String> extractX509IPAddresses(PKCS10CertificationRequest certReq) {
public static List<String> extractX509CSRIPAddresses(PKCS10CertificationRequest certReq) {

List<String> ipAddresses = new ArrayList<>();
Attribute[] attributes = certReq.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
Expand All @@ -780,6 +780,20 @@ public static List<String> extractX509IPAddresses(PKCS10CertificationRequest cer
return ipAddresses;
}

public static String extractX509CSRPublicKey(PKCS10CertificationRequest certReq) {

JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
PublicKey publicKey = null;
try {
publicKey = pemConverter.getPublicKey(certReq.getSubjectPublicKeyInfo());
} catch (PEMException ex) {
LOG.error("extractX509CSRPublicKey: unable to get public key: {}", ex.getMessage());
return null;
}

return convertToPEMFormat(publicKey);
}

public static String generateX509CSR(PrivateKey privateKey, String x500Principal,
GeneralName[] sanArray) throws OperatorCreationException, IOException {
final PublicKey publicKey = extractPublicKey(privateKey);
Expand Down Expand Up @@ -843,6 +857,41 @@ public static String extractX509CertCommonName(X509Certificate x509Cert) {
return cn;
}

public static List<String> extractX509CertDnsNames(X509Certificate x509Cert) {
Collection<List<?>> altNames = null;
try {
altNames = x509Cert.getSubjectAlternativeNames();
} catch (CertificateParsingException ex) {
LOG.error("extractX509IPAddresses: Caught CertificateParsingException when parsing certificate: "
+ ex.getMessage());
}

if (altNames == null) {
return Collections.emptyList();
}

List<String> dnsNames = new ArrayList<>();
for (@SuppressWarnings("rawtypes") List item : altNames) {
Integer type = (Integer) item.get(0);

// GeneralName ::= CHOICE {
// otherName [0] OtherName,
// rfc822Name [1] IA5String,
// dNSName [2] IA5String,
// x400Address [3] ORAddress,
// directoryName [4] Name,
// ediPartyName [5] EDIPartyName,
// uniformResourceIdentifier [6] IA5String,
// iPAddress [7] OCTET STRING,
// registeredID [8] OBJECT IDENTIFIER}

if (type == GeneralName.dNSName) {
dnsNames.add((String) item.get(1));
}
}
return dnsNames;
}

public static List<String> extractX509CertEmails(X509Certificate x509Cert) {
Collection<List<?>> altNames = null;
try {
Expand Down Expand Up @@ -877,8 +926,8 @@ public static List<String> extractX509CertEmails(X509Certificate x509Cert) {
}
return emails;
}

public static List<String> extractX509IPAddresses(X509Certificate x509Cert) {
public static List<String> extractX509CertIPAddresses(X509Certificate x509Cert) {

Collection<List<?>> altNames = null;
try {
Expand Down Expand Up @@ -914,6 +963,17 @@ public static List<String> extractX509IPAddresses(X509Certificate x509Cert) {
return ipAddresses;
}

public static String extractX509CertPublicKey(X509Certificate x509Cert) {

PublicKey publicKey = x509Cert.getPublicKey();
if (publicKey == null) {
LOG.error("extractX509CertPublicKey: unable to get public key");
return null;
}

return convertToPEMFormat(publicKey);
}

public static X509Certificate generateX509Certificate(PKCS10CertificationRequest certReq,
PrivateKey caPrivateKey, X509Certificate caCertificate, int validityTimeout,
boolean basicConstraints) {
Expand Down
Loading

0 comments on commit 145d19c

Please sign in to comment.