mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 10:28:22 +00:00
Merge branch 'logging' into activity-logs
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
name: Release Docker image for K8 operator
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: 🔧 Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: 🐋 Login to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: k8-operator
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: infisical/kubernetes-operator:latest
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
|
||||||
|
- name: Upload CRD manifest
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: dist/install-secrets-operator.yaml
|
||||||
|
tag: ${{ github.ref }}
|
||||||
@@ -13,6 +13,7 @@ import * as stripeController from './stripeController';
|
|||||||
import * as userActionController from './userActionController';
|
import * as userActionController from './userActionController';
|
||||||
import * as userController from './userController';
|
import * as userController from './userController';
|
||||||
import * as workspaceController from './workspaceController';
|
import * as workspaceController from './workspaceController';
|
||||||
|
import * as logController from './logController';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
@@ -29,5 +30,6 @@ export {
|
|||||||
stripeController,
|
stripeController,
|
||||||
userActionController,
|
userActionController,
|
||||||
userController,
|
userController,
|
||||||
workspaceController
|
workspaceController,
|
||||||
|
logController
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import {
|
||||||
|
Log
|
||||||
|
} from '../models';
|
||||||
|
|
||||||
|
|
||||||
|
export const getLogs = async (req: Request, res: Response) => {
|
||||||
|
// get logs
|
||||||
|
|
||||||
|
console.log('getLogs');
|
||||||
|
let logs;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
logs = await Log.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get audit logs'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
logs
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -38,7 +38,8 @@ import {
|
|||||||
password as passwordRouter,
|
password as passwordRouter,
|
||||||
stripe as stripeRouter,
|
stripe as stripeRouter,
|
||||||
integration as integrationRouter,
|
integration as integrationRouter,
|
||||||
integrationAuth as integrationAuthRouter
|
integrationAuth as integrationAuthRouter,
|
||||||
|
log as logRouter
|
||||||
} from './routes';
|
} from './routes';
|
||||||
|
|
||||||
const connectWithRetry = () => {
|
const connectWithRetry = () => {
|
||||||
@@ -92,6 +93,7 @@ app.use('/api/v1/password', passwordRouter);
|
|||||||
app.use('/api/v1/stripe', stripeRouter);
|
app.use('/api/v1/stripe', stripeRouter);
|
||||||
app.use('/api/v1/integration', integrationRouter);
|
app.use('/api/v1/integration', integrationRouter);
|
||||||
app.use('/api/v1/integration-auth', integrationAuthRouter);
|
app.use('/api/v1/integration-auth', integrationAuthRouter);
|
||||||
|
app.use('/api/v1/log', logRouter);
|
||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Token, { IToken } from './token';
|
|||||||
import User, { IUser } from './user';
|
import User, { IUser } from './user';
|
||||||
import UserAction, { IUserAction } from './userAction';
|
import UserAction, { IUserAction } from './userAction';
|
||||||
import Workspace, { IWorkspace } from './workspace';
|
import Workspace, { IWorkspace } from './workspace';
|
||||||
|
import Log, { ILog } from './log';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
BackupPrivateKey,
|
BackupPrivateKey,
|
||||||
@@ -41,5 +42,7 @@ export {
|
|||||||
UserAction,
|
UserAction,
|
||||||
IUserAction,
|
IUserAction,
|
||||||
Workspace,
|
Workspace,
|
||||||
IWorkspace
|
IWorkspace,
|
||||||
|
Log,
|
||||||
|
ILog
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Schema, model, Types } from 'mongoose';
|
||||||
|
|
||||||
|
export interface ILog {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
user: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
event: string;
|
||||||
|
source: string;
|
||||||
|
ipAddress: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need a way to store payload info for each
|
||||||
|
// log
|
||||||
|
|
||||||
|
// which secret is being ref etc.
|
||||||
|
|
||||||
|
const logSchema = new Schema<ILog>(
|
||||||
|
{
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User'
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace'
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
source: { // should this just be a payload attr?
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
ipAddress: { // store in bytes?
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Log = model<ILog>('Log', logSchema);
|
||||||
|
|
||||||
|
export default Log;
|
||||||
@@ -14,6 +14,7 @@ import password from './password';
|
|||||||
import stripe from './stripe';
|
import stripe from './stripe';
|
||||||
import integration from './integration';
|
import integration from './integration';
|
||||||
import integrationAuth from './integrationAuth';
|
import integrationAuth from './integrationAuth';
|
||||||
|
import log from './log';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
signup,
|
signup,
|
||||||
@@ -31,5 +32,6 @@ export {
|
|||||||
password,
|
password,
|
||||||
stripe,
|
stripe,
|
||||||
integration,
|
integration,
|
||||||
integrationAuth
|
integrationAuth,
|
||||||
|
log
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../middleware';
|
||||||
|
import { logController } from '../controllers';
|
||||||
|
|
||||||
|
// TODO: workspaceId validation
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId',
|
||||||
|
requireAuth,
|
||||||
|
validateRequest,
|
||||||
|
logController.getLogs
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Circle CI"
|
||||||
|
---
|
||||||
|
|
||||||
|
Coming soon.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Fly.io"
|
||||||
|
---
|
||||||
|
|
||||||
|
Coming soon.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Render"
|
||||||
|
---
|
||||||
|
|
||||||
|
Coming soon.
|
||||||
@@ -13,12 +13,14 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
|
|||||||
| Kubernetes | Platform | Coming soon |
|
| Kubernetes | Platform | Coming soon |
|
||||||
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
||||||
| [Vercel](/integrations/cloud/vercel) | Cloud | Coming soon |
|
| [Vercel](/integrations/cloud/vercel) | Cloud | Coming soon |
|
||||||
|
| [Render](/integrations/cloud/render) | Cloud | Coming soon |
|
||||||
|
| [Fly.io](/integrations/cloud/flyio) | Cloud | Coming soon |
|
||||||
| AWS | Cloud | Coming soon |
|
| AWS | Cloud | Coming soon |
|
||||||
| GCP | Cloud | Coming soon |
|
| GCP | Cloud | Coming soon |
|
||||||
| Azure | Cloud | Coming soon |
|
| Azure | Cloud | Coming soon |
|
||||||
| DigitalOcean | Cloud | Coming soon |
|
| DigitalOcean | Cloud | Coming soon |
|
||||||
| GitLab | CI/CD | Coming soon |
|
| GitLab | CI/CD | Coming soon |
|
||||||
| CircleCI | CI/CD | Coming soon |
|
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon |
|
||||||
| TravisCI | CI/CD | Coming soon |
|
| TravisCI | CI/CD | Coming soon |
|
||||||
| GitHub Actions | CI/CD | Coming soon |
|
| GitHub Actions | CI/CD | Coming soon |
|
||||||
| Jenkins | CI/CD | Coming soon |
|
| Jenkins | CI/CD | Coming soon |
|
||||||
|
|||||||
+9
-1
@@ -131,7 +131,15 @@
|
|||||||
"group": "Cloud",
|
"group": "Cloud",
|
||||||
"pages": [
|
"pages": [
|
||||||
"integrations/cloud/heroku",
|
"integrations/cloud/heroku",
|
||||||
"integrations/cloud/vercel"
|
"integrations/cloud/vercel",
|
||||||
|
"integrations/cloud/render",
|
||||||
|
"integrations/cloud/flyio"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "CI/CD",
|
||||||
|
"pages": [
|
||||||
|
"integrations/cicd/circleci"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ helm install infisical-helm-charts/<name-of-helm-chart>
|
|||||||
|
|
||||||
#### Available chart names
|
#### Available chart names
|
||||||
- infisical
|
- infisical
|
||||||
|
- secrets-operator
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: secrets-operator
|
||||||
|
description: A Helm chart for Infisical secrets
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.1.0
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
# It is recommended to use it with quotes.
|
||||||
|
appVersion: "0.1.0"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "secrets-operator.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "secrets-operator.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "secrets-operator.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "secrets-operator.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "secrets-operator.chart" . }}
|
||||||
|
{{ include "secrets-operator.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "secrets-operator.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "secrets-operator.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "secrets-operator.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "secrets-operator.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-controller-manager
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: rbac
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-controller-manager
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: manager
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
control-plane: controller-manager
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.controllerManager.replicas }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
{{- include "secrets-operator.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
{{- include "secrets-operator.selectorLabels" . | nindent 8 }}
|
||||||
|
annotations:
|
||||||
|
kubectl.kubernetes.io/default-container: manager
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/arch
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
- ppc64le
|
||||||
|
- s390x
|
||||||
|
- key: kubernetes.io/os
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- linux
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- --secure-listen-address=0.0.0.0:8443
|
||||||
|
- --upstream=http://127.0.0.1:8080/
|
||||||
|
- --logtostderr=true
|
||||||
|
- --v=0
|
||||||
|
env:
|
||||||
|
- name: KUBERNETES_CLUSTER_DOMAIN
|
||||||
|
value: {{ .Values.kubernetesClusterDomain }}
|
||||||
|
image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag
|
||||||
|
| default .Chart.AppVersion }}
|
||||||
|
name: kube-rbac-proxy
|
||||||
|
ports:
|
||||||
|
- containerPort: 8443
|
||||||
|
name: https
|
||||||
|
protocol: TCP
|
||||||
|
resources: {{- toYaml .Values.controllerManager.kubeRbacProxy.resources | nindent
|
||||||
|
10 }}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
- args:
|
||||||
|
- --health-probe-bind-address=:8081
|
||||||
|
- --metrics-bind-address=127.0.0.1:8080
|
||||||
|
- --leader-elect
|
||||||
|
command:
|
||||||
|
- /manager
|
||||||
|
env:
|
||||||
|
- name: KUBERNETES_CLUSTER_DOMAIN
|
||||||
|
value: {{ .Values.kubernetesClusterDomain }}
|
||||||
|
image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
|
||||||
|
| default .Chart.AppVersion }}
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 20
|
||||||
|
name: manager
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /readyz
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10
|
||||||
|
}}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
serviceAccountName: {{ include "secrets-operator.fullname" . }}-controller-manager
|
||||||
|
terminationGracePeriodSeconds: 10
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: infisicalsecrets.secrets.infisical.com
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.10.0
|
||||||
|
labels:
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
group: secrets.infisical.com
|
||||||
|
names:
|
||||||
|
kind: InfisicalSecret
|
||||||
|
listKind: InfisicalSecretList
|
||||||
|
plural: infisicalsecrets
|
||||||
|
singular: infisicalsecret
|
||||||
|
scope: Namespaced
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
description: InfisicalSecret is the Schema for the infisicalsecrets API
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: 'APIVersion defines the versioned schema of this representation
|
||||||
|
of an object. Servers should convert recognized schemas to the latest
|
||||||
|
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: 'Kind is a string value representing the REST resource this
|
||||||
|
object represents. Servers may infer this from the endpoint the client
|
||||||
|
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
spec:
|
||||||
|
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||||
|
properties:
|
||||||
|
environment:
|
||||||
|
description: The Infisical environment such as dev, prod, testing
|
||||||
|
type: string
|
||||||
|
infisicalToken:
|
||||||
|
properties:
|
||||||
|
secretName:
|
||||||
|
description: The name of the Kubernetes Secret
|
||||||
|
type: string
|
||||||
|
secretNamespace:
|
||||||
|
description: The name space where the Kubernetes Secret is located
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- secretName
|
||||||
|
- secretNamespace
|
||||||
|
type: object
|
||||||
|
managedSecret:
|
||||||
|
properties:
|
||||||
|
secretName:
|
||||||
|
description: The name of the Kubernetes Secret
|
||||||
|
type: string
|
||||||
|
secretNamespace:
|
||||||
|
description: The name space where the Kubernetes Secret is located
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- secretName
|
||||||
|
- secretNamespace
|
||||||
|
type: object
|
||||||
|
projectId:
|
||||||
|
description: The Infisical project id
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- environment
|
||||||
|
- projectId
|
||||||
|
type: object
|
||||||
|
status:
|
||||||
|
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
||||||
|
properties:
|
||||||
|
conditions:
|
||||||
|
items:
|
||||||
|
description: "Condition contains details for one aspect of the current
|
||||||
|
state of this API Resource. --- This struct is intended for direct
|
||||||
|
use as an array at the field path .status.conditions. For example,
|
||||||
|
\n type FooStatus struct{ // Represents the observations of a foo's
|
||||||
|
current state. // Known .status.conditions.type are: \"Available\",
|
||||||
|
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||||
|
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||||
|
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||||
|
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||||
|
properties:
|
||||||
|
lastTransitionTime:
|
||||||
|
description: lastTransitionTime is the last time the condition
|
||||||
|
transitioned from one status to another. This should be when
|
||||||
|
the underlying condition changed. If that is not known, then
|
||||||
|
using the time when the API field changed is acceptable.
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
description: message is a human readable message indicating details
|
||||||
|
about the transition. This may be an empty string.
|
||||||
|
maxLength: 32768
|
||||||
|
type: string
|
||||||
|
observedGeneration:
|
||||||
|
description: observedGeneration represents the .metadata.generation
|
||||||
|
that the condition was set based upon. For instance, if .metadata.generation
|
||||||
|
is currently 12, but the .status.conditions[x].observedGeneration
|
||||||
|
is 9, the condition is out of date with respect to the current
|
||||||
|
state of the instance.
|
||||||
|
format: int64
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
reason:
|
||||||
|
description: reason contains a programmatic identifier indicating
|
||||||
|
the reason for the condition's last transition. Producers of
|
||||||
|
specific condition types may define expected values and meanings
|
||||||
|
for this field, and whether the values are considered a guaranteed
|
||||||
|
API. The value should be a CamelCase string. This field may
|
||||||
|
not be empty.
|
||||||
|
maxLength: 1024
|
||||||
|
minLength: 1
|
||||||
|
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
description: status of the condition, one of True, False, Unknown.
|
||||||
|
enum:
|
||||||
|
- "True"
|
||||||
|
- "False"
|
||||||
|
- Unknown
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
|
--- Many .condition.type values are consistent across resources
|
||||||
|
like Available, but because arbitrary conditions can be useful
|
||||||
|
(see .node.status.conditions), the ability to deconflict is
|
||||||
|
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||||
|
maxLength: 316
|
||||||
|
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- lastTransitionTime
|
||||||
|
- message
|
||||||
|
- reason
|
||||||
|
- status
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- conditions
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
|
status:
|
||||||
|
acceptedNames:
|
||||||
|
kind: ""
|
||||||
|
plural: ""
|
||||||
|
conditions: []
|
||||||
|
storedVersions: []
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-leader-election-role
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: rbac
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- coordination.k8s.io
|
||||||
|
resources:
|
||||||
|
- leases
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-leader-election-rolebinding
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: rbac
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: '{{ include "secrets-operator.fullname" . }}-leader-election-role'
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
|
||||||
|
namespace: '{{ .Release.Namespace }}'
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-manager-role
|
||||||
|
labels:
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- deployments
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- secrets.infisical.com
|
||||||
|
resources:
|
||||||
|
- infisicalsecrets
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- secrets.infisical.com
|
||||||
|
resources:
|
||||||
|
- infisicalsecrets/finalizers
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- secrets.infisical.com
|
||||||
|
resources:
|
||||||
|
- infisicalsecrets/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-manager-rolebinding
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: rbac
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: '{{ include "secrets-operator.fullname" . }}-manager-role'
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
|
||||||
|
namespace: '{{ .Release.Namespace }}'
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-metrics-reader
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: kube-rbac-proxy
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
rules:
|
||||||
|
- nonResourceURLs:
|
||||||
|
- /metrics
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-controller-manager-metrics-service
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: kube-rbac-proxy
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
control-plane: controller-manager
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.metricsService.type }}
|
||||||
|
selector:
|
||||||
|
control-plane: controller-manager
|
||||||
|
{{- include "secrets-operator.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
{{- .Values.metricsService.ports | toYaml | nindent 2 -}}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-proxy-role
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: kube-rbac-proxy
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- authentication.k8s.io
|
||||||
|
resources:
|
||||||
|
- tokenreviews
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- apiGroups:
|
||||||
|
- authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- subjectaccessreviews
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "secrets-operator.fullname" . }}-proxy-rolebinding
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: kube-rbac-proxy
|
||||||
|
app.kubernetes.io/created-by: k8-operator
|
||||||
|
app.kubernetes.io/part-of: k8-operator
|
||||||
|
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: '{{ include "secrets-operator.fullname" . }}-proxy-role'
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: '{{ include "secrets-operator.fullname" . }}-controller-manager'
|
||||||
|
namespace: '{{ .Release.Namespace }}'
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
controllerManager:
|
||||||
|
kubeRbacProxy:
|
||||||
|
image:
|
||||||
|
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
||||||
|
tag: v0.13.1
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 128Mi
|
||||||
|
requests:
|
||||||
|
cpu: 5m
|
||||||
|
memory: 64Mi
|
||||||
|
manager:
|
||||||
|
image:
|
||||||
|
repository: infisical/kubernetes-operator
|
||||||
|
tag: latest
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 128Mi
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 64Mi
|
||||||
|
replicas: 1
|
||||||
|
kubernetesClusterDomain: cluster.local
|
||||||
|
metricsService:
|
||||||
|
ports:
|
||||||
|
- name: https
|
||||||
|
port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: https
|
||||||
|
type: ClusterIP
|
||||||
@@ -15,6 +15,7 @@ RUN go mod download
|
|||||||
COPY main.go main.go
|
COPY main.go main.go
|
||||||
COPY api/ api/
|
COPY api/ api/
|
||||||
COPY controllers/ controllers/
|
COPY controllers/ controllers/
|
||||||
|
COPY packages/ packages/
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ all: build
|
|||||||
help: ## Display this help.
|
help: ## Display this help.
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
|
||||||
|
## Chart
|
||||||
|
helm-chart:
|
||||||
|
$(KUSTOMIZE) build config/default | helmify ../helm-charts/secrets-operator
|
||||||
|
|
||||||
##@ Development
|
##@ Development
|
||||||
|
|
||||||
.PHONY: manifests
|
.PHONY: manifests
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import (
|
|||||||
type KubeSecretReference struct {
|
type KubeSecretReference struct {
|
||||||
// The name of the Kubernetes Secret
|
// The name of the Kubernetes Secret
|
||||||
// +kubebuilder:validation:Required
|
// +kubebuilder:validation:Required
|
||||||
Name string `json:"name"`
|
SecretName string `json:"secretName"`
|
||||||
|
|
||||||
// The name space where the Kubernetes Secret is located
|
// The name space where the Kubernetes Secret is located
|
||||||
// +kubebuilder:validation:Required
|
// +kubebuilder:validation:Required
|
||||||
Namespace string `json:"namespace,omitempty"`
|
SecretNamespace string `json:"secretNamespace"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InfisicalSecretSpec defines the desired state of InfisicalSecret
|
// InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||||
@@ -21,11 +21,11 @@ type InfisicalSecretSpec struct {
|
|||||||
|
|
||||||
// The Infisical project id
|
// The Infisical project id
|
||||||
// +kubebuilder:validation:Required
|
// +kubebuilder:validation:Required
|
||||||
ProjectId string `json:"projectId,omitempty"`
|
ProjectId string `json:"projectId"`
|
||||||
|
|
||||||
// The Infisical environment such as dev, prod, testing
|
// The Infisical environment such as dev, prod, testing
|
||||||
// +kubebuilder:validation:Required
|
// +kubebuilder:validation:Required
|
||||||
Environment string `json:"environment,omitempty"`
|
Environment string `json:"environment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InfisicalSecretStatus defines the observed state of InfisicalSecret
|
// InfisicalSecretStatus defines the observed state of InfisicalSecret
|
||||||
|
|||||||
@@ -40,29 +40,34 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
infisicalToken:
|
infisicalToken:
|
||||||
properties:
|
properties:
|
||||||
name:
|
secretName:
|
||||||
description: The name of the Kubernetes Secret
|
description: The name of the Kubernetes Secret
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
secretNamespace:
|
||||||
description: The name space where the Kubernetes Secret is located
|
description: The name space where the Kubernetes Secret is located
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- name
|
- secretName
|
||||||
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
managedSecret:
|
managedSecret:
|
||||||
properties:
|
properties:
|
||||||
name:
|
secretName:
|
||||||
description: The name of the Kubernetes Secret
|
description: The name of the Kubernetes Secret
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
secretNamespace:
|
||||||
description: The name space where the Kubernetes Secret is located
|
description: The name space where the Kubernetes Secret is located
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- name
|
- secretName
|
||||||
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
projectId:
|
projectId:
|
||||||
description: The Infisical project id
|
description: The Infisical project id
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- environment
|
||||||
|
- projectId
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
||||||
|
|||||||
@@ -9,4 +9,11 @@ metadata:
|
|||||||
app.kubernetes.io/created-by: k8-operator
|
app.kubernetes.io/created-by: k8-operator
|
||||||
name: infisicalsecret-sample
|
name: infisicalsecret-sample
|
||||||
spec:
|
spec:
|
||||||
# TODO(user): Add fields here
|
projectId: 62faf98ae0b05e8529b5da46
|
||||||
|
environment: dev
|
||||||
|
infisicalToken:
|
||||||
|
secretName: service-token
|
||||||
|
secretNamespace: default
|
||||||
|
managedSecret:
|
||||||
|
secretName: managed-secret
|
||||||
|
secretNamespace: default
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR)
|
err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR)
|
||||||
|
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Unable to reconcile Infisical Secret and will try again")
|
log.Error(err, "Unable to reconcile Infisical Secret and will try again")
|
||||||
return ctrl.Result{
|
return ctrl.Result{
|
||||||
@@ -61,12 +62,15 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctrl.Result{}, nil
|
// Sync again after the specified time
|
||||||
|
return ctrl.Result{
|
||||||
|
RequeueAfter: time.Minute,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&secretsv1alpha1.InfisicalSecret{}).
|
For(&secretsv1alpha1.InfisicalSecret{}). // TODO we should also be watching secrets with the name specifed
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||||
api "github.com/Infisical/infisical/k8-operator/packages/api"
|
api "github.com/Infisical/infisical/k8-operator/packages/api"
|
||||||
models "github.com/Infisical/infisical/k8-operator/packages/models"
|
models "github.com/Infisical/infisical/k8-operator/packages/models"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
@@ -27,20 +29,20 @@ func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Co
|
|||||||
|
|
||||||
func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
|
func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
|
||||||
tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||||
Namespace: infisicalSecret.Spec.ManagedSecret.Namespace,
|
Namespace: infisicalSecret.Spec.InfisicalToken.SecretNamespace,
|
||||||
Name: infisicalSecret.Spec.ManagedSecret.Name,
|
Name: infisicalSecret.Spec.InfisicalToken.SecretName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to read infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.ManagedSecret.Name, infisicalSecret.Spec.ManagedSecret.Namespace, err)
|
return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.ManagedSecret.SecretName, infisicalSecret.Spec.ManagedSecret.SecretNamespace, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME]
|
infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME]
|
||||||
if infisicalServiceToken == nil {
|
if infisicalServiceToken == nil {
|
||||||
return "", fmt.Errorf("the Infisical token is not set in the Kubernetes secret. Please add the key [%s] with the corresponding token value.", INFISICAL_TOKEN_SECRET_KEY_NAME)
|
return "", fmt.Errorf("the Infisical token is not set in the Kubernetes secret. Please add the key [%s] with the corresponding token value", INFISICAL_TOKEN_SECRET_KEY_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(infisicalServiceToken), nil
|
return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []models.SingleEnvironmentVariable) error {
|
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []models.SingleEnvironmentVariable) error {
|
||||||
@@ -52,8 +54,8 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
|||||||
// create a new secret as specified by the managed secret spec of CRD
|
// create a new secret as specified by the managed secret spec of CRD
|
||||||
newKubeSecretInstance := &corev1.Secret{
|
newKubeSecretInstance := &corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: infisicalSecret.Spec.ManagedSecret.Name,
|
Name: infisicalSecret.Spec.ManagedSecret.SecretName,
|
||||||
Namespace: infisicalSecret.Spec.ManagedSecret.Namespace,
|
Namespace: infisicalSecret.Spec.ManagedSecret.SecretNamespace,
|
||||||
},
|
},
|
||||||
Type: "Opaque",
|
Type: "Opaque",
|
||||||
Data: plainProcessedSecrets,
|
Data: plainProcessedSecrets,
|
||||||
@@ -86,13 +88,14 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context
|
|||||||
|
|
||||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
|
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
|
||||||
infisicalToken, err := r.GetInfisicalToken(ctx, infisicalSecret)
|
infisicalToken, err := r.GetInfisicalToken(ctx, infisicalSecret)
|
||||||
|
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
|
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||||
Name: infisicalSecret.Spec.ManagedSecret.Name,
|
Name: infisicalSecret.Spec.ManagedSecret.SecretName,
|
||||||
Namespace: infisicalSecret.Spec.ManagedSecret.Namespace,
|
Namespace: infisicalSecret.Spec.ManagedSecret.SecretNamespace,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
@@ -100,6 +103,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
|||||||
}
|
}
|
||||||
|
|
||||||
secretsFromApi, err := api.GetAllEnvironmentVariables(infisicalSecret.Spec.ProjectId, infisicalSecret.Spec.Environment, infisicalToken)
|
secretsFromApi, err := api.GetAllEnvironmentVariables(infisicalSecret.Spec.ProjectId, infisicalSecret.Spec.Environment, infisicalToken)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -111,3 +115,59 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conditions
|
||||||
|
|
||||||
|
func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) {
|
||||||
|
if infisicalSecret.Status.Conditions == nil {
|
||||||
|
infisicalSecret.Status.Conditions = []metav1.Condition{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorToConditionOn == nil {
|
||||||
|
meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
|
||||||
|
Type: "secrets.infisical.com/ReadyToSyncSecrets",
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
Reason: "OK",
|
||||||
|
Message: "Infisical controller has started syncing your secrets",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
|
||||||
|
Type: "secrets.infisical.com/ReadyToSyncSecrets",
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
Reason: "Error",
|
||||||
|
Message: fmt.Sprintf("Failed to update secret because: %v", errorToConditionOn),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.Client.Status().Update(ctx, infisicalSecret)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Could not set condition", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) {
|
||||||
|
if infisicalSecret.Status.Conditions == nil {
|
||||||
|
infisicalSecret.Status.Conditions = []metav1.Condition{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorToConditionOn == nil {
|
||||||
|
meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
|
||||||
|
Type: "secrets.infisical.com/LoadedInfisicalToken",
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
Reason: "OK",
|
||||||
|
Message: "Infisical controller has located the Infisical token in provided Kubernetes secret",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
|
||||||
|
Type: "secrets.infisical.com/LoadedInfisicalToken",
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
Reason: "Error",
|
||||||
|
Message: fmt.Sprintf("Failed to load Infisical Token because: %v", errorToConditionOn),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.Client.Status().Update(ctx, infisicalSecret)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Could not set condition for LoadedInfisicalToken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@ require (
|
|||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/api v0.25.0 // indirect
|
k8s.io/api v0.25.0
|
||||||
k8s.io/apiextensions-apiserver v0.25.0 // indirect
|
k8s.io/apiextensions-apiserver v0.25.0 // indirect
|
||||||
k8s.io/component-base v0.25.0 // indirect
|
k8s.io/component-base v0.25.0 // indirect
|
||||||
k8s.io/klog/v2 v2.70.1 // indirect
|
k8s.io/klog/v2 v2.70.1 // indirect
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Infisical/infisical/k8-operator/packages/crypto"
|
"github.com/Infisical/infisical/k8-operator/packages/crypto"
|
||||||
@@ -20,7 +21,7 @@ func GetAllEnvironmentVariables(projectId string, envName string, infisicalToken
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return envsFromApi, nil
|
return SubstituteSecrets(envsFromApi), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string) ([]models.SingleEnvironmentVariable, error) {
|
func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string) ([]models.SingleEnvironmentVariable, error) {
|
||||||
@@ -106,3 +107,73 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string,
|
|||||||
|
|
||||||
return listOfEnv, nil
|
return listOfEnv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string {
|
||||||
|
if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range secrets {
|
||||||
|
if secret.Key == variableWeAreLookingFor {
|
||||||
|
regex := regexp.MustCompile(`\${([^\}]*)}`)
|
||||||
|
variablesToPopulate := regex.FindAllString(secret.Value, -1)
|
||||||
|
|
||||||
|
// case: variable is a constant so return its value
|
||||||
|
if len(variablesToPopulate) == 0 {
|
||||||
|
return secret.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
valueToEdit := secret.Value
|
||||||
|
for _, variableWithSign := range variablesToPopulate {
|
||||||
|
variableWithoutSign := strings.Trim(variableWithSign, "}")
|
||||||
|
variableWithoutSign = strings.Trim(variableWithoutSign, "${")
|
||||||
|
|
||||||
|
// case: reference to self
|
||||||
|
if variableWithoutSign == secret.Key {
|
||||||
|
hashMapOfSelfRefs[variableWithoutSign] = variableWithoutSign
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
var expandedVariableValue string
|
||||||
|
|
||||||
|
if preComputedVariable, found := hashMapOfCompleteVariables[variableWithoutSign]; found {
|
||||||
|
expandedVariableValue = preComputedVariable
|
||||||
|
} else {
|
||||||
|
expandedVariableValue = getExpandedEnvVariable(secrets, variableWithoutSign, hashMapOfCompleteVariables, hashMapOfSelfRefs)
|
||||||
|
hashMapOfCompleteVariables[variableWithoutSign] = expandedVariableValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If after expanding all the vars above, is the current var a self ref? if so no replacement needed for it
|
||||||
|
if _, found := hashMapOfSelfRefs[variableWithoutSign]; found {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
valueToEdit = strings.ReplaceAll(valueToEdit, variableWithSign, expandedVariableValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueToEdit
|
||||||
|
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${" + variableWeAreLookingFor + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable {
|
||||||
|
hashMapOfCompleteVariables := make(map[string]string)
|
||||||
|
hashMapOfSelfRefs := make(map[string]string)
|
||||||
|
expandedSecrets := []models.SingleEnvironmentVariable{}
|
||||||
|
|
||||||
|
for _, secret := range secrets {
|
||||||
|
expandedVariable := getExpandedEnvVariable(secrets, secret.Key, hashMapOfCompleteVariables, hashMapOfSelfRefs)
|
||||||
|
expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{
|
||||||
|
Key: secret.Key,
|
||||||
|
Value: expandedVariable,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return expandedSecrets
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user