github.com/zntrio/harp/v2@v2.0.9/pkg/container/identity/codec.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package identity 19 20 import ( 21 "encoding/json" 22 "fmt" 23 "io" 24 "time" 25 26 validation "github.com/go-ozzo/ozzo-validation/v4" 27 "github.com/go-ozzo/ozzo-validation/v4/is" 28 29 "github.com/zntrio/harp/v2/pkg/container/identity/key" 30 "github.com/zntrio/harp/v2/pkg/sdk/types" 31 ) 32 33 const ( 34 apiVersion = "harp.elastic.co/v1" 35 kind = "ContainerIdentity" 36 ) 37 38 // ----------------------------------------------------------------------------- 39 40 type PrivateKeyGeneratorFunc func(io.Reader) (*key.JSONWebKey, string, error) 41 42 // New identity from description. 43 func New(random io.Reader, description string, generator PrivateKeyGeneratorFunc) (*Identity, []byte, error) { 44 // Check arguments 45 if err := validation.Validate(description, validation.Required, is.ASCII); err != nil { 46 return nil, nil, fmt.Errorf("unable to create identity with invalid description: %w", err) 47 } 48 49 // Delegate to generator 50 jwk, encodedPub, err := generator(random) 51 if err != nil { 52 return nil, nil, fmt.Errorf("unable to generate identity private key: %w", err) 53 } 54 55 // Encode JWK as json 56 payload, err := json.Marshal(jwk) 57 if err != nil { 58 return nil, nil, fmt.Errorf("unable to serialize identity keypair: %w", err) 59 } 60 61 // Prepae identity object 62 id := &Identity{ 63 APIVersion: apiVersion, 64 Kind: kind, 65 Timestamp: time.Now().UTC(), 66 Description: description, 67 Public: encodedPub, 68 } 69 70 // Encode to json for signature 71 protected, err := json.Marshal(id) 72 if err != nil { 73 return nil, nil, fmt.Errorf("unable to serialize identity for signature: %w", err) 74 } 75 76 // Sign the protected data 77 sig, err := jwk.Sign(protected) 78 if err != nil { 79 return nil, nil, fmt.Errorf("unable to sign protected data: %w", err) 80 } 81 82 // Auto-assign the signature 83 id.Signature = sig 84 85 // Return unsealed identity 86 return id, payload, nil 87 } 88 89 // FromReader extract identity instance from reader. 90 func FromReader(r io.Reader) (*Identity, error) { 91 // Check arguments 92 if types.IsNil(r) { 93 return nil, fmt.Errorf("unable to read nil reader") 94 } 95 96 // Convert input as a map 97 var input Identity 98 if err := json.NewDecoder(r).Decode(&input); err != nil { 99 return nil, fmt.Errorf("unable to decode input JSON: %w", err) 100 } 101 102 // Check component 103 if input.Private == nil { 104 return nil, fmt.Errorf("invalid identity: missing private component") 105 } 106 107 // Validate self signature 108 if errVerify := input.Verify(); errVerify != nil { 109 return nil, fmt.Errorf("unable to verify identity: %w", errVerify) 110 } 111 112 // Return no error 113 return &input, nil 114 }