Skip to content

Commit

Permalink
add kube sidecar builder
Browse files Browse the repository at this point in the history
  • Loading branch information
niqdev committed Oct 10, 2023
1 parent ebd1a91 commit 0e81c5d
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 29 deletions.
33 changes: 4 additions & 29 deletions pkg/client/kubernetes/builder_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package kubernetes

import (
"bytes"
"log"
"testing"

"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/kubernetes/scheme"
)

func TestBuildResources(t *testing.T) {
Expand Down Expand Up @@ -159,8 +154,8 @@ status:
actualService.TypeMeta = metav1.TypeMeta{Kind: "Service", APIVersion: "v1"}

assert.NoError(t, err)
assert.YAMLEqf(t, expectedDeployment, objectToYaml(actualDeployment), "unexpected deployment")
assert.YAMLEqf(t, expectedService, objectToYaml(actualService), "unexpected service")
assert.YAMLEqf(t, expectedDeployment, ObjectToYaml(actualDeployment), "unexpected deployment")
assert.YAMLEqf(t, expectedService, ObjectToYaml(actualService), "unexpected service")
}

func TestBuildJob(t *testing.T) {
Expand Down Expand Up @@ -229,25 +224,5 @@ status: {}
// fix model
actualJob.TypeMeta = metav1.TypeMeta{Kind: "Job", APIVersion: "batch/v1"}

assert.YAMLEqf(t, expectedJob, objectToYaml(actualJob), "unexpected job")
}

func objectToYaml(object runtime.Object) string {
buffer := new(bytes.Buffer)
printer := printers.YAMLPrinter{}
if err := printer.PrintObj(object, buffer); err != nil {
log.Fatalf("objectToYaml: %#v\n", err)
}
return buffer.String()
}

// https://github.com/kubernetes/client-go/issues/193
// https://medium.com/@harshjniitr/reading-and-writing-k8s-resource-as-yaml-in-golang-81dc8c7ea800
func yamlToDeployment(data string) *appsv1.Deployment {
decoder := scheme.Codecs.UniversalDeserializer().Decode
object, _, err := decoder([]byte(data), nil, nil)
if err != nil {
log.Fatalf("yamlToDeployment: %#v\n", err)
}
return object.(*appsv1.Deployment)
assert.YAMLEqf(t, expectedJob, ObjectToYaml(actualJob), "unexpected job")
}
31 changes: 31 additions & 0 deletions pkg/client/kubernetes/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package kubernetes

import (
"bytes"
"log"

appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/kubernetes/scheme"
)

func ObjectToYaml(object runtime.Object) string {
buffer := new(bytes.Buffer)
printer := printers.YAMLPrinter{}
if err := printer.PrintObj(object, buffer); err != nil {
log.Fatalf("ObjectToYaml: %#v\n", err)
}
return buffer.String()
}

// https://github.com/kubernetes/client-go/issues/193
// https://medium.com/@harshjniitr/reading-and-writing-k8s-resource-as-yaml-in-golang-81dc8c7ea800
func YamlToDeployment(data string) *appsv1.Deployment {
decoder := scheme.Codecs.UniversalDeserializer().Decode
object, _, err := decoder([]byte(data), nil, nil)
if err != nil {
log.Fatalf("YamlToDeployment: %#v\n", err)
}
return object.(*appsv1.Deployment)
}
86 changes: 86 additions & 0 deletions pkg/common/kubernetes/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package kubernetes

import (
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

commonModel "github.com/hckops/hckctl/pkg/common/model"
)

const (
secretBasePath = "/secrets"
sidecarVpnTunnelVolume = "tun-device-volume"
sidecarVpnTunnelPath = "/dev/net/tun"
sidecarVpnSecretVolume = "sidecar-vpn-volume"
sidecarVpnSecretPath = "/secrets/openvpn/client.ovpn"
sidecarVpnSecretKey = "openvpn-config"
)

func buildSidecarVpnSecretName(containerName string) string {
return fmt.Sprintf("%s-sidecar-vpn-secret", containerName)
}

func buildSidecarVpnSecret(namespace, containerName, secretValue string) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: buildSidecarVpnSecretName(containerName),
Namespace: namespace,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{sidecarVpnSecretKey: []byte(secretValue)},
}
}

func buildSidecarVpnContainer() corev1.Container {
return corev1.Container{
Name: "sidecar-vpn",
Image: commonModel.SidecarVpnImageName,
ImagePullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{
{Name: "OPENVPN_CONFIG", Value: sidecarVpnSecretPath},
},
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"NET_ADMIN"},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: sidecarVpnTunnelVolume,
MountPath: sidecarVpnTunnelPath,
ReadOnly: true,
},
{
Name: sidecarVpnSecretVolume,
MountPath: secretBasePath,
ReadOnly: true,
},
},
}
}

func buildSidecarVpnVolumes(containerName string) []corev1.Volume {
return []corev1.Volume{
{
Name: sidecarVpnTunnelVolume,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: sidecarVpnTunnelPath,
},
},
},
{
Name: sidecarVpnSecretVolume,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: buildSidecarVpnSecretName(containerName),
Items: []corev1.KeyToPath{
{Key: sidecarVpnSecretKey, Path: sidecarVpnSecretPath},
},
},
},
},
}
}
89 changes: 89 additions & 0 deletions pkg/common/kubernetes/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package kubernetes

import (
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/hckops/hckctl/pkg/client/kubernetes"
"github.com/hckops/hckctl/pkg/util"
)

func TestBuildSidecarVpnSecret(t *testing.T) {

expected := `
apiVersion: v1
data:
openvpn-config: bXktdmFsdWU=
kind: Secret
metadata:
creationTimestamp: null
name: my-container-name-sidecar-vpn-secret
namespace: my-namespace
type: Opaque
`

actual := buildSidecarVpnSecret("my-namespace", "my-container-name", "my-value")
// fix model
actual.TypeMeta = metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}

assert.YAMLEqf(t, expected, kubernetes.ObjectToYaml(actual), "unexpected secret")

decoded, ok := util.Base64Decode("bXktdmFsdWU=")
assert.True(t, ok)
assert.Equal(t, "my-value", decoded)
}

func TestBuildSidecarVpnPod(t *testing.T) {

expected := `
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
spec:
containers:
- env:
- name: OPENVPN_CONFIG
value: /secrets/openvpn/client.ovpn
image: hckops/alpine-openvpn:latest
imagePullPolicy: IfNotPresent
name: sidecar-vpn
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
volumeMounts:
- mountPath: /dev/net/tun
name: tun-device-volume
readOnly: true
- mountPath: /secrets
name: sidecar-vpn-volume
readOnly: true
volumes:
- hostPath:
path: /dev/net/tun
name: tun-device-volume
- name: sidecar-vpn-volume
secret:
items:
- key: openvpn-config
path: /secrets/openvpn/client.ovpn
secretName: main-container-sidecar-vpn-secret
status: {}
`

actual := &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{buildSidecarVpnContainer()},
Volumes: buildSidecarVpnVolumes("main-container"),
},
}
// fix model
actual.TypeMeta = metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"}

assert.YAMLEqf(t, expected, kubernetes.ObjectToYaml(actual), "unexpected pod")
}
23 changes: 23 additions & 0 deletions pkg/util/string.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"encoding/base64"
"fmt"
"os"
"regexp"
Expand Down Expand Up @@ -98,3 +99,25 @@ func Expand(raw string, inputs map[string]string) (string, error) {

return expanded, err
}

func Base64Encode(value string) string {
// alternative with "len"
//encoded := make([]byte, base64.StdEncoding.EncodedLen(len(value)))
//base64.StdEncoding.Encode(encoded, []byte(value))
//return string(encoded)

return base64.StdEncoding.EncodeToString([]byte(value))
}

func Base64Decode(value string) (string, bool) {
// alternative with "len"
//decoded := make([]byte, base64.StdEncoding.DecodedLen(len(value)))
//count, err := base64.StdEncoding.Decode(decoded, []byte(value))
//return string(decoded)

decoded, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return "", false
}
return string(decoded), true
}
10 changes: 10 additions & 0 deletions pkg/util/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ import (
func TestToLowerKebabCase(t *testing.T) {
assert.Equal(t, "hckops-my-test_value-example", ToLowerKebabCase(" hCKops/my-tEst_value$ExamplE\t"))
}

func TestBase64(t *testing.T) {
value := "hello world"
encoded := Base64Encode(value)
assert.Equal(t, "aGVsbG8gd29ybGQ=", encoded)

decoded, ok := Base64Decode(encoded)
assert.True(t, ok)
assert.Equal(t, value, decoded)
}

0 comments on commit 0e81c5d

Please sign in to comment.