diff --git a/README.md b/README.md index 964f5cb..e80ceba 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Learn more: - Create signed and encrypted contracts - Support contract expiry with CA certificates - Validate contract schemas + - Create Gzipped & Encoded initdata for HPCC Peerpod - **Archive Management** - Generate Base64 tar archives of `docker-compose.yaml` or `pods.yaml` @@ -75,6 +76,7 @@ Learn more: - Validate network-config schemas for on-premise deployments - Support HPVS, HPCR RHVS, and HPCC Peer Pod configurations + ## Installation Download the CLI tool for your operating system from the [releases page](https://github.com/ibm-hyper-protect/contract-cli/releases/latest). @@ -168,6 +170,14 @@ contract-cli validate-contract \ --type hpvs ``` +### Create initdata annotation from signed & encrypted contract + +```bash +# Create initdata annotation +contract-cli initdata \ + --in signed_encrypted_contract.yaml +``` + ## Usage ```bash @@ -194,6 +204,7 @@ Available Commands: encrypt-string Encrypt string in Hyper Protect format get-certificate Extract specific certificate version from download output help Help about any command + initdata Gzip and Encoded initdata annotation image Get HPCR image details from IBM Cloud validate-contract Validate contract schema validate-encryption-certificate validate encryption certificate @@ -230,6 +241,7 @@ The [`samples/`](samples/) directory contains example configurations: - [Attestation Records](samples/attestation/) - [Network Configuration](samples/network/) - [Docker Compose Examples](samples/tgz/) +- [Sample Singed & Encrypted Contract](samples/hpcc/signed-encrypt-hpcc.yaml) ## Related Projects diff --git a/cmd/initdata.go b/cmd/initdata.go new file mode 100644 index 0000000..8ca3f93 --- /dev/null +++ b/cmd/initdata.go @@ -0,0 +1,60 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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 cmd + +import ( + "log" + + "github.com/ibm-hyper-protect/contract-cli/common" + "github.com/ibm-hyper-protect/contract-cli/lib/initdata" + "github.com/spf13/cobra" +) + +// initdataCmd represents the hpccinidata command +var initdataCmd = &cobra.Command{ + Use: initdata.ParameterName, + Short: initdata.ParameterShortDescription, + Long: initdata.ParameterLongDescription, + Run: func(cmd *cobra.Command, args []string) { + inputDataPath, outputPath, err := initdata.ValidateInput(cmd) + if err != nil { + log.Fatal(err) + } + + gzipInitdata, err := initdata.GenerateInitdata(inputDataPath) + if err != nil { + log.Fatal(err) + } + + err = initdata.PrintInitdata(gzipInitdata, outputPath) + if err != nil { + log.Fatal(err) + } + }, +} + +// init - cobra init function +func init() { + rootCmd.AddCommand(initdataCmd) + requiredFlags := map[string]bool{ + "in": true, + } + + initdataCmd.PersistentFlags().String(initdata.InputFlagName, "", initdata.InputFlagDescription) + initdataCmd.PersistentFlags().String(initdata.OutputFlagName, "", initdata.OutputFlagDescription) + common.SetCustomHelpTemplate(initdataCmd, requiredFlags) + common.SetCustomErrorTemplate(initdataCmd) +} diff --git a/cmd/initdata_test.go b/cmd/initdata_test.go new file mode 100644 index 0000000..0bc2c2b --- /dev/null +++ b/cmd/initdata_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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 cmd + +import ( + "bytes" + "testing" + + "github.com/ibm-hyper-protect/contract-cli/lib/initdata" + "github.com/stretchr/testify/assert" +) + +const ( + //Initdata Test Case. + sampleSignedEncryptedContract = "../samples/hpcc/signed-encrypt-hpcc.yaml" + sampleGzippedInitdataValue = "../build/gzipped-initdata" +) + +var ( + sampleGzippedInitdataCmd = []string{initdata.ParameterName, "--in", sampleSignedEncryptedContract, "--out", sampleGzippedInitdataValue} +) + +// Testcase to check gzipped initdata funtionality. +func TestGzippedInitdata(t *testing.T) { + // Capture output + buf := new(bytes.Buffer) + rootCmd.SetOut(buf) + rootCmd.SetErr(buf) + + rootCmd.SetArgs(sampleGzippedInitdataCmd) + err := initdataCmd.Execute() + + assert.NoError(t, err) +} diff --git a/docs/README.md b/docs/README.md index 8bde6c2..1e0c97b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,7 @@ Complete command reference and usage guide for the Hyper Protect Contract CLI. - [image](#image) - [validate-contract](#validate-contract) - [validate-network](#validate-network) + - [initdata](#initdata) - [Common Workflows](#common-workflows) - [Troubleshooting](#troubleshooting) - [Examples](#examples) @@ -609,6 +610,32 @@ contract-cli validate-encryption-certificate --in encryption-cert.crt --- +### initdata +Create initdata annotation from signed and encrypted contract for Hyper Protect Confidential Containers PeerPod solution + +#### Usage + +```bash +contract-cli initdata [flags] +``` + +#### Flags + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--in` | string | Yes | Path to signed & encrypted contract YAML file | +| `--out` | string | No | Path to store gzipped & encoded initdata value | +| `-h, --help` | - | No | Display help information | + +#### Examples + +**Create Hpcc Initdata from signed & encrypted contract** +```bash +contract-cli initdata --in signed_encrypted_contract.yaml +``` + +--- + ## Common Workflows ### Complete Contract Generation Workflow @@ -731,6 +758,7 @@ The [`samples/`](../samples/) directory contains working examples: - **[Attestation Records](../samples/attestation/)** - Example attestation files - **[Network Configuration](../samples/network/)** - Network config examples - **[Docker Compose](../samples/tgz/)** - Compose file examples +- **[Signed & Encrypted Contract](../samples/hpcc/signed-encrypt-hpcc.yaml)** - Signed & Encrypted hpcc contract --- diff --git a/lib/initdata/initdata.go b/lib/initdata/initdata.go new file mode 100644 index 0000000..7109b40 --- /dev/null +++ b/lib/initdata/initdata.go @@ -0,0 +1,85 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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 initdata + +import ( + "fmt" + + "github.com/ibm-hyper-protect/contract-cli/common" + "github.com/ibm-hyper-protect/contract-go/v2/contract" + "github.com/spf13/cobra" +) + +const ( + ParameterName = "initdata" + ParameterShortDescription = "Gzip and Encoded initdata annotation" + ParameterLongDescription = `Gzip and Encoded initdata annotation` + + InputFlagName = "in" + InputFlagDescription = "Path of Signed and Encrypted contract" + + OutputFlagName = "out" + OutputFlagDescription = "Path to save Gzipped and encoded initdata value" +) + +// ValidateInput - function to validate inputs of initdata +func ValidateInput(cmd *cobra.Command) (string, string, error) { + inputData, err := cmd.Flags().GetString(InputFlagName) + if err != nil { + return "", "", err + } + + if inputData == "" { + err := fmt.Errorf("Error: required flag '--in' is missing") + common.SetMandatoryFlagError(cmd, err) + } + + outputPath, err := cmd.Flags().GetString(OutputFlagName) + if err != nil { + return "", "", err + } + return inputData, outputPath, nil +} + +// GenerateInitdata - function to generate gzipped initdata +func GenerateInitdata(inputDataPath string) (string, error) { + if !common.CheckFileFolderExists(inputDataPath) { + return "", fmt.Errorf("the contract path doesn't exist") + } + inputData, err := common.ReadDataFromFile(inputDataPath) + if err != nil { + return "", err + } + gzipInitdata, _, _, err := contract.HpccInitdata(inputData) + if err != nil { + return "", err + } + return gzipInitdata, nil +} + +// PrintInitdata - function to print generated gzipped initdata value +func PrintInitdata(gzippedData, outputPath string) error { + if outputPath != "" { + err := common.WriteDataToFile(outputPath, gzippedData) + if err != nil { + return err + } + fmt.Println("Successfully generated gzipped initdata annotation") + } else { + fmt.Println(gzippedData) + } + return nil +} diff --git a/samples/hpcc/signed-encrypt-hpcc.yaml b/samples/hpcc/signed-encrypt-hpcc.yaml new file mode 100644 index 0000000..80da8c6 --- /dev/null +++ b/samples/hpcc/signed-encrypt-hpcc.yaml @@ -0,0 +1,3 @@ +env: hyper-protect-basic.njLuN8QAL9VceUyiYmDzSyAPvVowCTyw4qDJb/Y3z8sR884gPPjHESXnkJAgDnLRl1P24xVzOXe117IQGp+ayQyrHA+40dA+RF64WNs2ZGPHoVmrIfsrEnS8WjrHla00vaN00xCdoA2DehlVYWDISN/HD9vcdPzh7nc6oPoU9/iyWuNWNJUp/QOuJipg5Yu2he5eBf0R7zVdMghKiJQ86sW1blgsUIiWnZsy9Hia+yHLmDE4OsnkgciJJYMvzarMWHOjwwEyhKPNxoVGfxM8f2pp13gx9QY2tWGIVSDFqyWJudAKZ8yvDEVL3x1FlpxG9zcMFDmAbviZhKykMaDmbDeH60+SPneyUV7x/X132d+Nyd2DvCMS/37Z3vmqfpJSxsam5UCT68f1t5Znmk4MNBFcnMI3wFkdTfazUO+2qk1LwgLM2ZFdajtgIhMLua1UOTITiHRbvmAcLppucA7w1ZkqB2gFZCVVKvIcmOHeLYPklZiHTYPnQHlLeS90sk9YoM0BNL42L7JrtTs6EVjSAgVQxDruiERbsjpQy9KwltLOVjYEDQ4v0fpAxxg6GpKds3j9vcOI+9RlABTW10RgrIZsC+QjaB+mra1PFHvcHITkPjbSmtoXXDmD22YiNReK+bYpikvEOgFRi6srrYzOG6oNDygMUQ6+PIwncwkI9V0=.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 +envWorkloadSignature: ZM25jpFqS3/MRpYipUaTOW0Yy35jSo/SwuXMQfZJQnID6hMPwPS1hLZee5ST719J/GmpWmxah0PYbLZ2yf2qjzlnmJjH66aJfKhfAYSN93yT5rp++4sMlByyAavHNYcBNTs1lytMUbWP4U5zVuiIONgiGqPDGluGmNMOvH22lB7hCQxAcC1AZzv23ye2eYwPfVsFspwpe72JGn9UxSVVKHPntv/3Tok6UOoU/pXiR/eprodT3wQMbSCBqkB0xRn7pQyWF/5Mptw4mIqmnVPvCJDFZH4ksOy7+6yjfLvA6ce2OFzRIKDDEL7C/5KUFc15uCBS/Qs8D5xIDmbO0zwLw91gw+6F9JS6g63MjTWcwIF6bcF79Iu7qxJdukR5LYYgCZBFzE6Eae/31Mr6G1v6b48NEub6njcdF7b0gnUbvKEF0yD9NfocoDIiqXPXWRehxMgyTdRqQEVhO8shLny+PZmYvh0QiVJsEf3QWfEpiVVDw+wOfqTok9nheTlGsAEl0gTOgIwbULdKIyaGn+jdECdC1augYA5ebz/cOHWV9n0PjtuDeLdjTEHpKZysYoagF9RLf93nLbd7Nvr6I9Hmp2WvuUFTV8sXbuAAzZSaBv3S1Po+VtZjB4B4Y5+eV2G/JDqmUyU8RRfCiZWAcBW5GJMue9sGAio10iUTA+oliXc= +workload: hyper-protect-basic.et0k+AL9qgf9kSy+iH8z+b+3hRN9nw2J/T7zDeLcXfSfTAh31X2wAA4l4P7jqT23NFhjiJNApjy6J0xLVqHGnIGbINYTLchupBDfWSPCIh185OIBZqCxWawEt7xsbW2XNtjhQ50ErzPn1SB5pejnimZdYsvv+CLzJGZOWvoBnboBaQML60opp0jYNkFQtj6RRiTMfIAyTxNB0MKuvaJ3HDUK4mgrb1jAZ45tUDOJ6fs2M7r8sgNXojGLuqq2AMc6UrftIsBMehwwfZpxSE4wLdKhFyYMqdiZwSA6UEHBtReTGt2M1JtmZRb6DvPbH7bGqWQ3bOiQ5o+cC6M1E5C5hFC5yzMid8rQrZX13nG9JmEvUMduYAVwtIDl1lobW/FwIKhSEvphYJ5Hy/Xu7kJTBm15xIpDSfZ/MGbihtpd+J812sKEUIquwuoVgn1FlOoLtp8ZwJoM6/WBVilq/QkM7maqPwhKETLDVQh7OazpVJ6lGtvCa7kf93ojEoqqrGI3ok3IxnHAOLVdj7mG65Qx4AYdgqYoMOfX5DXe6Kv5Kzcki/3vn8YCwpD1bpxpbgM5QMx02i/Y20QAl5sjquq5zSaH7kDpVmHz3C4UyrDPxgpdC+/U7gDty9UVVuF/S8gG60zCiM2JPvWNJf8l3pYLt3P1C51zD2MGyz1FTd3ntfY=.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