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

feat: add support for contract expiry #112

Merged
merged 13 commits into from
Apr 8, 2024
Merged
6 changes: 6 additions & 0 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,11 @@ const (
KeyVersion = "version"
KeyVersions = "versions"

KeyExpiryDays = "expiry"
KeyCaCert = "cacert"
KeyCaKey = "cakey"
KeyCsrParams = "csrparams"
KeyCsrfile = "csrfile"

PrefixBasicEncoding = "hyper-protect-basic"
)
148 changes: 148 additions & 0 deletions datasource/datasource_contract_signingcert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package datasource

import (
"encoding/json"
"fmt"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/ibm-hyper-protect/terraform-provider-hpcr/common"
"github.com/ibm-hyper-protect/terraform-provider-hpcr/encrypt"
"gopkg.in/yaml.v3"
)

func ResourceContractEncryptedSigningCert() *schema.Resource {
return &schema.Resource{
Create: resourceContractEncryptedSigningCertCreate,
Read: resourceContractEncryptedSigningCertRead,
Delete: resourceContractEncryptedSigningCertDelete,
Schema: map[string]*schema.Schema{
// input parameters
common.KeyContract: &schemaContractIn,
common.KeyCert: &schemaCertIn,
common.KeyPrivKey: &schemaPrivKeyIn,
common.KeyExpiryDays: &schemaExpiryDaysIn,
common.KeyCaCert: &schemaCaCertIn,
common.KeyCaKey: &schemaCaKeyIn,
common.KeyCsrParams: &schemaCsrParamsIn,
common.KeyCsrfile: &schemaCsrFileIn,
// output parameters
common.KeyRendered: &schemaRenderedOut,
},
Description: "Generates an encrypted and signed user data field with contract expiry enabled",
}
}

func resourceContractEncryptedSigningCertCreate(d *schema.ResourceData, meta interface{}) error {
// OpenSSL check
err := encrypt.OpensslCheck()
if err != nil {
return fmt.Errorf("OpenSSL not installed correctly %s", err.Error())
}

contract := d.Get(common.KeyContract).(string)
encryptCertificate := d.Get(common.KeyCert).(string)
privateKey := d.Get(common.KeyPrivKey).(string)
expiryDays := d.Get(common.KeyExpiryDays).(int)
caCert := d.Get(common.KeyCaCert).(string)
caKey := d.Get(common.KeyCaKey).(string)
csrParams := d.Get(common.KeyCsrParams).(map[string]interface{})
csrData := d.Get(common.KeyCsrfile).(string)

if privateKey == "" {
return fmt.Errorf("private key missing")
}

if len(csrParams) != 0 && csrData != "" {
return fmt.Errorf("CSR parameters and csr.pem has been parsed together")
}

csrJsonStr, err := json.Marshal(csrParams)
if err != nil {
return fmt.Errorf("error working on CSR data %s", err.Error())
}
finalContract, err := EncryptAndSign(contract, encryptCertificate, privateKey, caCert, caKey, string(csrJsonStr), csrData, expiryDays)
if err != nil {
return fmt.Errorf("error generating contract %s", err.Error())
}
err = d.Set(common.KeyRendered, finalContract)
if err != nil {
return fmt.Errorf("error saving contract %s", err.Error())
}

newUUID := uuid.New()
d.SetId(newUUID.String())

return resourceContractEncryptedSigningCertRead(d, meta)
}

func resourceContractEncryptedSigningCertRead(d *schema.ResourceData, meta interface{}) error {

return nil
}

func resourceContractEncryptedSigningCertDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func EncryptAndSign(contract, encryptCert, privateKey, cacert, caKey, csrDataStr, csrPemData string, expiryDays int) (string, error) {
var contractMap map[string]interface{}

err := yaml.Unmarshal([]byte(contract), &contractMap)
if err != nil {
return "", err
}

randomPassword, err := encrypt.RandomPasswordGenerator()
if err != nil {
return "", err
}

encryptedRandomPassword, err := encrypt.EncryptPassword(randomPassword, encryptCert)
if err != nil {
return "", err
}

encryptedWorkload, err := encrypt.EncryptContract(randomPassword, contractMap["workload"].(map[string]interface{}))
if err != nil {
return "", err
}

finalWorkload := encrypt.EncryptFinalStr(encryptedRandomPassword, encryptedWorkload)

signingCert, err := encrypt.CreateSigningCert(privateKey, cacert, caKey, csrDataStr, csrPemData, expiryDays)
if err != nil {
return "", err
}

signingKeyInjectedEnv, err := encrypt.KeyValueInjector(contractMap["env"].(map[string]interface{}), "signingKey", signingCert)
if err != nil {
return "", err
}

var envMap map[string]interface{}

err = yaml.Unmarshal([]byte(signingKeyInjectedEnv), &envMap)
if err != nil {
return "", err
}

encryptedEnv, err := encrypt.EncryptContract(randomPassword, envMap)
if err != nil {
return "", err
}

finalEnv := encrypt.EncryptFinalStr(encryptedRandomPassword, encryptedEnv)

workloadEnvSignature, err := encrypt.SignContract(finalWorkload, finalEnv, privateKey)
if err != nil {
return "", err
}

finalContract, err := encrypt.GenFinalSignedContract(finalWorkload, finalEnv, workloadEnvSignature)
if err != nil {
return "", err
}

return finalContract, nil
}
122 changes: 122 additions & 0 deletions datasource/datasource_contract_signingcert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package datasource

import (
"encoding/json"
"fmt"
"io"
"os"
"testing"

"github.com/ibm-hyper-protect/terraform-provider-hpcr/data"
"github.com/stretchr/testify/assert"
)

func Commoner() (string, string, string, string, string, error) {
contractPath, err := os.Open("../samples/contracts/simple.yaml")
if err != nil {
fmt.Println("Error parsing contract - ", err)
}
defer contractPath.Close()

contract, err := io.ReadAll(contractPath)
if err != nil {
fmt.Println(err)
return "", "", "", "", "", err
}

encryptCert := data.DefaultCertificate

privateKeyPath, err := os.Open("../samples/contract-expiry/private.pem")
if err != nil {
fmt.Println("Error parsing Private Key - ", err)
return "", "", "", "", "", err
}
defer privateKeyPath.Close()

privateKey, err := io.ReadAll(privateKeyPath)
if err != nil {
fmt.Println(err)
return "", "", "", "", "", err
}

caCertPath, err := os.Open("../samples/contract-expiry/personal_ca.crt")
if err != nil {
fmt.Println("Error parsing CA certificate - ", err)
return "", "", "", "", "", err
}
defer caCertPath.Close()

caCert, err := io.ReadAll(caCertPath)
if err != nil {
fmt.Println(err)
return "", "", "", "", "", err
}

caKeyPath, err := os.Open("../samples/contract-expiry/personal_ca.key")
if err != nil {
fmt.Println("Error parsing CA certificate - ", err)
return "", "", "", "", "", err
}
defer caCertPath.Close()

caKey, err := io.ReadAll(caKeyPath)
if err != nil {
fmt.Println(err)
return "", "", "", "", "", err
}

return string(contract), encryptCert, string(privateKey), string(caCert), string(caKey), nil
}

func TestEncryptAndSign(t *testing.T) {

contract, encryptCert, privateKey, caCert, caKey, err := Commoner()
if err != nil {
fmt.Println(err)
}

csrDataMap := map[string]interface{}{
"country": "IN",
"state": "Karnataka",
"location": "Bangalore",
"org": "IBM",
"unit": "ISDL",
"domain": "HPVS",
"mail": "sashwat.k@ibm.com",
}
csrDataStr, err := json.Marshal(csrDataMap)
if err != nil {
fmt.Println(err)
}

expiryDays := 365

_, err = EncryptAndSign(contract, encryptCert, privateKey, caCert, caKey, string(csrDataStr), "", expiryDays)

assert.NoError(t, err)
}

func TestEncryptAndSignCsrFile(t *testing.T) {

contract, encryptCert, privateKey, caCert, caKey, err := Commoner()
if err != nil {
fmt.Println(err)
}

csrPath, err := os.Open("../samples/contract-expiry/csr.pem")
if err != nil {
fmt.Println("Error parsing CSR - ", err)
}
defer csrPath.Close()

csr, err := io.ReadAll(csrPath)
if err != nil {
fmt.Println(err)
}

expiryDays := 365

_, err = EncryptAndSign(contract, encryptCert, privateKey, caCert, caKey, "", string(csr), expiryDays)

assert.NoError(t, err)
}
39 changes: 39 additions & 0 deletions datasource/resource_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,45 @@ var (
ValidateDiagFunc: validation.DiagCertificate,
}

schemaExpiryDaysIn = schema.Schema{
Type: schema.TypeInt,
Description: "Number of days for contract to expire",
Required: true,
ForceNew: true,
}

schemaCaCertIn = schema.Schema{
Type: schema.TypeString,
Description: "CA Certificate used to generate signing certificate",
Required: true,
ForceNew: true,
ValidateDiagFunc: validation.DiagCertificate,
}

schemaCaKeyIn = schema.Schema{
Type: schema.TypeString,
Description: "CA Key used to generate singing certificate",
Required: true,
ForceNew: true,
ValidateDiagFunc: validation.DiagPrivateKey,
}

schemaCsrParamsIn = schema.Schema{
Type: schema.TypeMap,
Description: "CSR Parameters to generate signing certificate",
Optional: true,
ForceNew: true,
ValidateDiagFunc: validation.DiagCsrParams,
}

schemaCsrFileIn = schema.Schema{
Type: schema.TypeString,
Description: "CSR File to generate signing certificate",
Optional: true,
ForceNew: true,
ValidateDiagFunc: validation.DiagCsrFile,
}

schemaPrivKeyIn = schema.Schema{
Type: schema.TypeString,
Description: "Private key used to sign the contract. If omitted, a temporally signing key is created.",
Expand Down
Loading