Skip to content
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
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Learn more:
- Generate Base64-encoded data from text, JSON, initdata annotation and docker compose / podman play archives
- Create signed and encrypted & signed contracts
- Support contract expiry with CSR (Certificate Signing Request)
- Load built-in workload and env contract templates
- Validate contract schemas
- Decrypt encrypted text in Hyper Protect format
- Password-protected private key support for decrypting attestation records and generate signed contracts
Expand Down Expand Up @@ -140,6 +141,44 @@ func main() {
}
```

### Load Contract Templates

```go
package main

import (
"fmt"
"log"

"github.com/ibm-hyper-protect/contract-go/v2/contract"
)

func main() {
// Retrieve workload template only
workloadTemplate, err := contract.HpcrContractTemplate("workload")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Workload Template:\n%s\n", workloadTemplate)

// Retrieve env template only
envTemplate, err := contract.HpcrContractTemplate("env")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Env Template:\n%s\n", envTemplate)

// Retrieve combined contract scaffold:
// workload: | <workload template>
// env: | <env template>
contractTemplate, err := contract.HpcrContractTemplate("")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Combined Contract Template:\n%s\n", contractTemplate)
}
```

### Generate a Signed and Encrypted Contract

```go
Expand Down
91 changes: 91 additions & 0 deletions contract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package contract
import (
"bytes"
"fmt"
"path/filepath"
"runtime"
"strings"
"text/template"

"gopkg.in/yaml.v3"
Expand All @@ -29,6 +32,11 @@ import (

const (
emptyParameterErrStatement = "required parameter is empty"

// Contract template locations relative to this package.
contractTemplateDirPath = "template"
workloadTemplateFilePath = "workload.yaml"
envTemplateFilePath = "env.yaml"
)

// HPCC initdata.toml file template.
Expand Down Expand Up @@ -439,6 +447,54 @@ func HpcrContractSign(contract, privateKey, password string) (string, string, st
return finalContract, gen.GenerateSha256(contract), gen.GenerateSha256(finalContract), nil
}

// HpcrContractTemplate returns contract template content for workload, env, or both.
//
// Parameters:
// - templateType: "workload", "env", or "" (returns both templates combined)
//
// Returns:
// - Template content as string
// - Error if template type is unsupported or file read fails
func HpcrContractTemplate(templateType string) (string, error) {
switch templateType {
case "workload":
workloadTemplate, err := readHpcrTemplateFile(workloadTemplateFilePath)
if err != nil {
return "", fmt.Errorf("failed to read workload template - %v", err)
}
return workloadTemplate, nil
case "env":
envTemplate, err := readHpcrTemplateFile(envTemplateFilePath)
if err != nil {
return "", fmt.Errorf("failed to read env template - %v", err)
}
return envTemplate, nil
case "":
workloadTemplate, err := readHpcrTemplateFile(workloadTemplateFilePath)
if err != nil {
return "", fmt.Errorf("failed to read workload template - %v", err)
}

envTemplate, err := readHpcrTemplateFile(envTemplateFilePath)
if err != nil {
return "", fmt.Errorf("failed to read env template - %v", err)
}

var templateBuilder strings.Builder
templateBuilder.WriteString("workload: |\n")
templateBuilder.WriteString(indentTemplateContent(workloadTemplate))
if !strings.HasSuffix(workloadTemplate, "\n") {
templateBuilder.WriteString("\n")
}
templateBuilder.WriteString("env: |\n")
templateBuilder.WriteString(indentTemplateContent(envTemplate))

return templateBuilder.String(), nil
default:
return "", fmt.Errorf("unsupported template type: %s", templateType)
}
}

// HpccInitdata generates gzipped and Base64-encoded initdata for IBM Confidential Computing
// Containers for Red Hat OpenShift Container Platform (HPCC) peer pod deployments.
//
Expand Down Expand Up @@ -595,3 +651,38 @@ func encrypter(stringText, hyperProtectOs, encryptionCertificate string) (string

return enc.EncryptFinalStr(encodedEncryptedPassword, encryptedString), nil
}

// readHpcrTemplateFile resolves a template file path under contract/template and returns its content.
func readHpcrTemplateFile(fileName string) (string, error) {
_, currentFile, _, ok := runtime.Caller(0)
if !ok {
return "", fmt.Errorf("failed to resolve contract template path")
}

templatePath := filepath.Join(filepath.Dir(currentFile), contractTemplateDirPath, fileName)
templateContent, err := gen.ReadDataFromFile(templatePath)
if err != nil {
return "", fmt.Errorf("failed to read template file %s - %v", fileName, err)
}

return templateContent, nil
}

// indentTemplateContent adds two-space indentation to each line for YAML block scalar formatting.
func indentTemplateContent(content string) string {
if content == "" {
return " "
}

var indentedContent strings.Builder
for _, line := range strings.SplitAfter(content, "\n") {
if line == "" {
continue
}

indentedContent.WriteString(" ")
indentedContent.WriteString(line)
}

return indentedContent.String()
}
77 changes: 77 additions & 0 deletions contract/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package contract
import (
"encoding/json"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -76,6 +77,10 @@ const (
sampleEncryptedContract = "../samples/sign/contract.enc.yaml"
sampleEncryptedInputSha = "6a15ebc21cec4fc6a1e7317b4ca4de9cf71bb7a29d9f81d42cd8474b270c54a1"
sampleSignEncryptOutputSha = "001e7c38bf6f34c80e0c1f7ca42ef420667f09687038bbe0233badda9cc210af"

// Contract template fixtures used by template retrieval tests.
sampleWorkloadTemplatePath = "./template/workload.yaml"
sampleEnvTemplatePath = "./template/env.yaml"
)

var (
Expand Down Expand Up @@ -795,3 +800,75 @@ func TestHpcrVerifyContractInvalidSchema(t *testing.T) {
err := HpcrVerifyContract(invalidContract, "")
assert.Error(t, err)
}

// Testcase to check if HpcrContractTemplate() is able to return workload template content.
func TestHpcrContractTemplateWorkload(t *testing.T) {
expectedTemplate, err := gen.ReadDataFromFile(sampleWorkloadTemplatePath)
if err != nil {
t.Errorf("failed to read workload template file - %v", err)
}

workloadTemplate, err := HpcrContractTemplate("workload")
assert.NoError(t, err)
assert.Equal(t, expectedTemplate, workloadTemplate)
}

// Testcase to check if HpcrContractTemplate() is able to return env template content.
func TestHpcrContractTemplateEnv(t *testing.T) {
expectedTemplate, err := gen.ReadDataFromFile(sampleEnvTemplatePath)
if err != nil {
t.Errorf("failed to read env template file - %v", err)
}

envTemplate, err := HpcrContractTemplate("env")
assert.NoError(t, err)
assert.Equal(t, expectedTemplate, envTemplate)
}

// Testcase to check if HpcrContractTemplate() is able to return combined template content.
func TestHpcrContractTemplateCombined(t *testing.T) {
workloadTemplate, err := gen.ReadDataFromFile(sampleWorkloadTemplatePath)
if err != nil {
t.Errorf("failed to read workload template file - %v", err)
}

envTemplate, err := gen.ReadDataFromFile(sampleEnvTemplatePath)
if err != nil {
t.Errorf("failed to read env template file - %v", err)
}

expectedOutput := "workload: |\n" + indentTemplateBlockForTest(workloadTemplate)
if !strings.HasSuffix(workloadTemplate, "\n") {
expectedOutput += "\n"
}
expectedOutput += "env: |\n" + indentTemplateBlockForTest(envTemplate)

combinedTemplate, err := HpcrContractTemplate("")
assert.NoError(t, err)
assert.Equal(t, expectedOutput, combinedTemplate)
}

// Testcase to check if HpcrContractTemplate() handles invalid template type.
func TestHpcrContractTemplateInvalidType(t *testing.T) {
_, err := HpcrContractTemplate("invalid")
assert.EqualError(t, err, "unsupported template type: invalid")
}

// indentTemplateBlockForTest mirrors production indentation behavior for expected YAML output.
func indentTemplateBlockForTest(content string) string {
if content == "" {
return " "
}

var output strings.Builder
for _, line := range strings.SplitAfter(content, "\n") {
if line == "" {
continue
}

output.WriteString(" ")
output.WriteString(line)
}

return output.String()
}
19 changes: 19 additions & 0 deletions contract/template/env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type: env
logging:
# Use logRouter for ICL & syslog for syslog server (only one)
logRouter:
hostname: <host name of the service instance> /
iamApiKey: <iamApiKey of the service instance> / xxxx
port: <port of the service instance(443)
syslog:
hostname: ${RSYSLOG_SERVER_IP}
port: 6514
server: "${RSYSLOG_SERVER_ROOT_CA}"
cert: "${RSYSLOG_CLIENT_CA}"
key: "${RSYSLOG_CLIENT_KEY}"
volumes:
test:
seed: "seed_value_with_minimum_15_characters"
env:
<env-name>: "env-value"
signingKey: <signing key or certificate>
20 changes: 20 additions & 0 deletions contract/template/workload.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type: workload
auths:
<registry url>:
password: <password>
username: <user name>
# Use either compose or pods
play:
archive: <base64 TGZ of podman play support files>
compose:
archive: <base64 TGZ of docker compose support files>
images:
dct:
<docker image name (without the tag, an example is docker.io/redbookuser/s390x:)>:
notary: "<notary URL>"
publicKey: <docker content trust signed public key>
volumes:
<volume key>:
mount: "<data volume mount path>"
seed: "<Passphrase of the LUKS encryption>"
filesystem: "ext4"
71 changes: 71 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,77 @@ MIIEpAIBAAKCAQEA...

---

### HpcrContractTemplate

Returns built-in contract template content for `workload`, `env`, or both as a combined YAML scaffold.

**Package:** `github.com/ibm-hyper-protect/contract-go/v2/contract`

**Signature:**
```go
func HpcrContractTemplate(templateType string) (string, error)
```

**Parameters:**

| Parameter | Type | Required/Optional | Description |
|-----------|------|-------------------|-------------|
| `templateType` | `string` | Optional | Template selector: `"workload"`, `"env"`, or `""` (returns combined output) |

**Returns:**

| Return | Type | Description |
|--------|------|-------------|
| Template Content | `string` | YAML template content from `contract/template/workload.yaml`, `contract/template/env.yaml`, or both |
| Error | `error` | Error if template type is invalid or template files cannot be read |

**Example:**
```go
package main

import (
"fmt"
"log"

"github.com/ibm-hyper-protect/contract-go/v2/contract"
)

func main() {
workloadTemplate, err := contract.HpcrContractTemplate("workload")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Workload template:\n%s\n", workloadTemplate)

envTemplate, err := contract.HpcrContractTemplate("env")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Env template:\n%s\n", envTemplate)

combinedTemplate, err := contract.HpcrContractTemplate("")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Combined template:\n%s\n", combinedTemplate)
}
```

**Combined Output Format (`templateType == ""`):**
```yaml
workload: |
...content of workload.yaml...
env: |
...content of env.yaml...
```

**Common Errors:**
- `"unsupported template type: <value>"` - `templateType` is not one of `"workload"`, `"env"`, or `""`
- `"failed to read workload template"` - Unable to read `contract/template/workload.yaml`
- `"failed to read env template"` - Unable to read `contract/template/env.yaml`

---

### HpcrJson

Generates Base64-encoded representation of JSON data with integrity checksums.
Expand Down
Loading