mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 18:26:16 +00:00
feat: updated doc for k8s issuer
This commit is contained in:
@@ -21,20 +21,21 @@ A typical workflow for using the Infisical PKI Issuer to issue certificates for
|
|||||||
3. Installing `cert-manager` into your Kubernetes cluster.
|
3. Installing `cert-manager` into your Kubernetes cluster.
|
||||||
4. Installing the Infisical PKI Issuer controller into your Kubernetes cluster.
|
4. Installing the Infisical PKI Issuer controller into your Kubernetes cluster.
|
||||||
5. Creating an `Issuer` or `ClusterIssuer` resource in your Kubernetes cluster to represent the Infisical PKI issuer you wish to use.
|
5. Creating an `Issuer` or `ClusterIssuer` resource in your Kubernetes cluster to represent the Infisical PKI issuer you wish to use.
|
||||||
6. Creating a `Certificate` resource in your Kubernetes cluster to represent a certificate you wish to issue. As part of this step, you specify the Kubernetes `Secret` to create and store the issued certificate and private key.
|
6. Create an the approver policy to accept certificate request.
|
||||||
7. Consuming the issued certificate across your Kubernetes resources from the specified Kubernetes `Secret`.
|
7. Creating a `Certificate` resource in your Kubernetes cluster to represent a certificate you wish to issue. As part of this step, you specify the Kubernetes `Secret` to create and store the issued certificate and private key.
|
||||||
|
8. Consuming the issued certificate across your Kubernetes resources from the specified Kubernetes `Secret`.
|
||||||
|
|
||||||
## Guide
|
## Guide
|
||||||
|
|
||||||
In the following steps, we explore how to install the Infisical PKI Issuer using [kubectl](https://github.com/kubernetes/kubectl) and use it to obtain certificates for your Kubernetes resources.
|
In the following steps, we explore how to install the Infisical PKI Issuer using [kubectl](https://github.com/kubernetes/kubectl) and use it to obtain certificates for your Kubernetes resources.
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Create an identity in Infisical">
|
<Step title="Create an identity in Infisical">
|
||||||
|
|
||||||
Follow the instructions [here](/documentation/platform/identities/universal-auth) to configure a [machine identity](/documentation/platform/identities/machine-identities) in Infisical with Universal Auth.
|
Follow the instructions [here](/documentation/platform/identities/universal-auth) to configure a [machine identity](/documentation/platform/identities/machine-identities) in Infisical with Universal Auth.
|
||||||
|
|
||||||
By the end of this step, you should have a **Client ID** and **Client Secret** on hand as part of the Universal Auth configuration for the Infisical PKI Issuer to authenticate with Infisical; this will be useful in steps 4 and 5.
|
By the end of this step, you should have a **Client ID** and **Client Secret** on hand as part of the Universal Auth configuration for the Infisical PKI Issuer to authenticate with Infisical; this will be useful in steps 4 and 5.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Currently, the Infisical PKI Issuer only supports authenticating with Infisical via the [Universal Auth](/documentation/platform/identities/universal-auth) authentication method.
|
Currently, the Infisical PKI Issuer only supports authenticating with Infisical via the [Universal Auth](/documentation/platform/identities/universal-auth) authentication method.
|
||||||
|
|
||||||
@@ -43,14 +44,14 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
</Step>
|
</Step>
|
||||||
<Step title="Install cert-manager">
|
<Step title="Install cert-manager">
|
||||||
Install `cert-manager` into your Kubernetes cluster by following the instructions [here](https://cert-manager.io/docs/installation/) or by running the following command:
|
Install `cert-manager` into your Kubernetes cluster by following the instructions [here](https://cert-manager.io/docs/installation/) or by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml
|
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml
|
||||||
```
|
```
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Install the Issuer Controller">
|
<Step title="Install the Issuer Controller">
|
||||||
Install the Infisical PKI Issuer controller into your Kubernetes cluster by running the following command:
|
Install the Infisical PKI Issuer controller into your Kubernetes cluster by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl apply -f https://raw.githubusercontent.com/Infisical/infisical-issuer/main/build/install.yaml
|
kubectl apply -f https://raw.githubusercontent.com/Infisical/infisical-issuer/main/build/install.yaml
|
||||||
```
|
```
|
||||||
@@ -76,7 +77,7 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
data:
|
data:
|
||||||
clientSecret: <client_secret>
|
clientSecret: <client_secret>
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl apply -f secret-issuer.yaml
|
kubectl apply -f secret-issuer.yaml
|
||||||
```
|
```
|
||||||
@@ -84,7 +85,7 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Create Infisical PKI Issuer">
|
<Step title="Create Infisical PKI Issuer">
|
||||||
Next, create the Infisical PKI Issuer by filling out `url`, `clientId`, either `caId` or `certificateTemplateId`, and applying the following configuration file for the `Issuer` resource.
|
Next, create the Infisical PKI Issuer by filling out `url`, `clientId`, `projectId` or `certificateTemplateName`, and applying the following configuration file for the `Issuer` resource.
|
||||||
This configuration file specifies the connection details to your Infisical PKI CA to be used for issuing certificates.
|
This configuration file specifies the connection details to your Infisical PKI CA to be used for issuing certificates.
|
||||||
|
|
||||||
```yaml infisical-issuer.yaml
|
```yaml infisical-issuer.yaml
|
||||||
@@ -95,8 +96,8 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
namespace: <namespace_you_want_to_issue_certificates_in>
|
namespace: <namespace_you_want_to_issue_certificates_in>
|
||||||
spec:
|
spec:
|
||||||
url: "https://app.infisical.com" # the URL of your Infisical instance
|
url: "https://app.infisical.com" # the URL of your Infisical instance
|
||||||
caId: <ca_id> # the ID of the CA you want to use to issue certificates
|
projectId: <project_id> # the ID of the project you want to use to issue certificates
|
||||||
certificateTemplateId: <certificate_template_id> # the ID of the certificate template you want to use to issue certificates against
|
certificateTemplateName: <certificate_template_name> # the name of the certificate template you want to use to issue certificates against
|
||||||
authentication:
|
authentication:
|
||||||
universalAuth:
|
universalAuth:
|
||||||
clientId: <client_id> # the Client ID from step 1
|
clientId: <client_id> # the Client ID from step 1
|
||||||
@@ -104,20 +105,11 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
name: "issuer-infisical-client-secret"
|
name: "issuer-infisical-client-secret"
|
||||||
key: "clientSecret"
|
key: "clientSecret"
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
kubectl apply -f infisical-issuer.yaml
|
kubectl apply -f infisical-issuer.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
<Warning>
|
|
||||||
The Infisical PKI Issuer supports issuing certificates against a specific CA or a specific certificate template.
|
|
||||||
|
|
||||||
For this reason, you should only fill in the `caId` or the `certificateTemplateId` field but not both.
|
|
||||||
|
|
||||||
We recommend using the `certificateTemplateId` field to issue certificates against a specific [certificate template](/documentation/platform/pki/certificate-templates)
|
|
||||||
since templates let you enforce constraints on issued certificates and may have alerting policies bound to them.
|
|
||||||
</Warning>
|
|
||||||
|
|
||||||
You can check that the issuer was created successfully by running the following command:
|
You can check that the issuer was created successfully by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -128,16 +120,60 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
NAME AGE
|
NAME AGE
|
||||||
issuer-infisical 21h
|
issuer-infisical 21h
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
An `Issuer` is a namespaced resource, and it is not possible to issue certificates from an `Issuer` in a different namespace.
|
An `Issuer` is a namespaced resource, and it is not possible to issue certificates from an `Issuer` in a different namespace.
|
||||||
This means you will need to create an `Issuer` in each namespace you wish to obtain `Certificates` in.
|
This means you will need to create an `Issuer` in each namespace you wish to obtain `Certificates` in.
|
||||||
|
|
||||||
If you want to create a single `Issuer` that can be consumed in multiple namespaces, you should consider creating a `ClusterIssuer` resource. This is almost identical to the `Issuer` resource, however is non-namespaced so it can be used to issue `Certificates` across all namespaces.
|
If you want to create a single `Issuer` that can be consumed in multiple namespaces, you should consider creating a `ClusterIssuer` resource. This is almost identical to the `Issuer` resource, however is non-namespaced so it can be used to issue `Certificates` across all namespaces.
|
||||||
|
|
||||||
You can read more about the `Issuer` and `ClusterIssuer` resources [here](https://cert-manager.io/docs/configuration/).
|
You can read more about the `Issuer` and `ClusterIssuer` resources [here](https://cert-manager.io/docs/configuration/).
|
||||||
</Note>
|
</Note>
|
||||||
</Step>
|
</Step>
|
||||||
|
<Step title="Create Approver Policy">
|
||||||
|
If you create a `CertificateRequest` now, you'll notice it's neither approved nor denied. This is expected because by default cert-manager approver controller requires an approver-policy.
|
||||||
|
|
||||||
|
To enable approval, create the following YAML file and apply it:
|
||||||
|
|
||||||
|
```yaml infisical-approver-policy.yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: infisical-issuer-approver
|
||||||
|
rules:
|
||||||
|
# Permission to approve or deny CertificateRequests for signers in cert-manager.io API group
|
||||||
|
- apiGroups: ['cert-manager.io']
|
||||||
|
resources: ['signers']
|
||||||
|
verbs: ['approve']
|
||||||
|
resourceNames:
|
||||||
|
# Grant approval permissions for namespaced issuers
|
||||||
|
- "issuers.infisical-issuer.infisical.com/default.issuer-infisical"
|
||||||
|
# Grant approval permissions for cluster-scoped issuers
|
||||||
|
- "clusterissuers.infisical-issuer.infisical.com/clusterissuer-infisical"
|
||||||
|
---
|
||||||
|
# Bind the cert-manager service account to the new role
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: infisical-issuer-approver-binding
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: cert-manager
|
||||||
|
namespace: cert-manager
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: infisical-issuer-approver
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
kubectl apply -f infisical-approver-policy.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration creates a `ClusterRole` named `infisical-issuer-approver` that grants approval permissions for specific Infisical issuer types. It then binds this role to the cert-manager service account, allowing it to approve certificate requests from your Infisical issuers.
|
||||||
|
|
||||||
|
For information, check out [cert manager approval policy doc](https://cert-manager.io/docs/policy/approval/approver-policy/).
|
||||||
|
</Step>
|
||||||
<Step title="Create Certificate">
|
<Step title="Create Certificate">
|
||||||
|
|
||||||
Finally, create a `Certificate` by applying the following configuration file.
|
Finally, create a `Certificate` by applying the following configuration file.
|
||||||
@@ -162,7 +198,7 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
duration: 48h # the ttl for the certificate
|
duration: 48h # the ttl for the certificate
|
||||||
renewBefore: 12h # the time before the certificate expiry that the certificate should be automatically renewed
|
renewBefore: 12h # the time before the certificate expiry that the certificate should be automatically renewed
|
||||||
```
|
```
|
||||||
|
|
||||||
The above sample configuration file specifies a certificate to be issued with the common name `certificate-by-issuer.example.com` and ECDSA private key using the P-256 curve, valid for 48 hours; the certificate will be automatically renewed by `cert-manager` 12 hours before expiry.
|
The above sample configuration file specifies a certificate to be issued with the common name `certificate-by-issuer.example.com` and ECDSA private key using the P-256 curve, valid for 48 hours; the certificate will be automatically renewed by `cert-manager` 12 hours before expiry.
|
||||||
The certificate is issued by the issuer `issuer-infisical` created in the previous step and the resulting certificate and private key will be stored in a secret named `certificate-by-issuer`.
|
The certificate is issued by the issuer `issuer-infisical` created in the previous step and the resulting certificate and private key will be stored in a secret named `certificate-by-issuer`.
|
||||||
|
|
||||||
@@ -181,7 +217,7 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
</Step>
|
</Step>
|
||||||
<Step title="Use Certificate in Kubernetes Secret">
|
<Step title="Use Certificate in Kubernetes Secret">
|
||||||
Since the actual certificate and private key are stored in a Kubernetes secret, we can check that the secret was created successfully by running the following command:
|
Since the actual certificate and private key are stored in a Kubernetes secret, we can check that the secret was created successfully by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get secret certificate-by-issuer -n <namespace_of_your_certificate>
|
kubectl get secret certificate-by-issuer -n <namespace_of_your_certificate>
|
||||||
```
|
```
|
||||||
@@ -190,9 +226,9 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
NAME TYPE DATA AGE
|
NAME TYPE DATA AGE
|
||||||
certificate-by-issuer kubernetes.io/tls 2 26h
|
certificate-by-issuer kubernetes.io/tls 2 26h
|
||||||
```
|
```
|
||||||
|
|
||||||
We can `describe` the secret to get more information about it:
|
We can `describe` the secret to get more information about it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl describe secret certificate-by-issuer -n default
|
kubectl describe secret certificate-by-issuer -n default
|
||||||
```
|
```
|
||||||
@@ -201,14 +237,14 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
Name: certificate-by-issuer
|
Name: certificate-by-issuer
|
||||||
Namespace: default
|
Namespace: default
|
||||||
Labels: controller.cert-manager.io/fao=true
|
Labels: controller.cert-manager.io/fao=true
|
||||||
Annotations: cert-manager.io/alt-names:
|
Annotations: cert-manager.io/alt-names:
|
||||||
cert-manager.io/certificate-name: certificate-by-issuer
|
cert-manager.io/certificate-name: certificate-by-issuer
|
||||||
cert-manager.io/common-name: certificate-by-issuer.example.com
|
cert-manager.io/common-name: certificate-by-issuer.example.com
|
||||||
cert-manager.io/ip-sans:
|
cert-manager.io/ip-sans:
|
||||||
cert-manager.io/issuer-group: infisical-issuer.infisical.com
|
cert-manager.io/issuer-group: infisical-issuer.infisical.com
|
||||||
cert-manager.io/issuer-kind: Issuer
|
cert-manager.io/issuer-kind: Issuer
|
||||||
cert-manager.io/issuer-name: issuer-infisical
|
cert-manager.io/issuer-name: issuer-infisical
|
||||||
cert-manager.io/uri-sans:
|
cert-manager.io/uri-sans:
|
||||||
|
|
||||||
Type: kubernetes.io/tls
|
Type: kubernetes.io/tls
|
||||||
|
|
||||||
@@ -218,17 +254,18 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
tls.crt: 2380 bytes
|
tls.crt: 2380 bytes
|
||||||
tls.key: 227 bytes
|
tls.key: 227 bytes
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, `ca.crt` is the Root CA certificate, `tls.crt` is the requested certificate followed by the certificate chain, and `tls.key` is the private key for the certificate.
|
Here, `ca.crt` is the Root CA certificate, `tls.crt` is the requested certificate followed by the certificate chain, and `tls.key` is the private key for the certificate.
|
||||||
|
|
||||||
We can decode the certificate and print it out using `openssl`:
|
We can decode the certificate and print it out using `openssl`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get secret certificate-by-issuer -n default -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout
|
kubectl get secret certificate-by-issuer -n default -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout
|
||||||
```
|
```
|
||||||
|
|
||||||
In any case, the certificate is ready to be used as Kubernetes Secret by your Kubernetes resources.
|
In any case, the certificate is ready to be used as Kubernetes Secret by your Kubernetes resources.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
@@ -236,15 +273,24 @@ In the following steps, we explore how to install the Infisical PKI Issuer using
|
|||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="What fields can be configured on the Certificate resource?">
|
<Accordion title="What fields can be configured on the Certificate resource?">
|
||||||
The full list of the fields supported on the `Certificate` resource can be found in the API reference documentation [here](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
The full list of the fields supported on the `Certificate` resource can be found in the API reference documentation [here](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Currently, not all fields are supported by the Infisical PKI Issuer.
|
Currently, not all fields are supported by the Infisical PKI Issuer.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="Can certificates be renewed automatically?">
|
<Accordion title="Can certificates be renewed automatically?">
|
||||||
Yes. `cert-manager` will automatically renew certificates according to the `renewBefore` threshold of expiry as
|
Yes. `cert-manager` will automatically renew certificates according to the `renewBefore` threshold of expiry as
|
||||||
specified in the corresponding `Certificate` resource.
|
specified in the corresponding `Certificate` resource.
|
||||||
|
|
||||||
You can read more about the `renewBefore` field [here](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
You can read more about the `renewBefore` field [here](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</AccordionGroup>
|
<Accordion title="Why is my CertificateRequest not being approved, showing 'CertificateRequest has not been approved yet. Ignoring.'?">
|
||||||
|
If you see log messages similar to:
|
||||||
|
```
|
||||||
|
"CertificateRequest has not been approved yet. Ignoring.","controller":"certificaterequest","controllerGroup":"cert-manager.io","controllerKind":"CertificateRequest","CertificateRequest":{"name":"skynet-infisical-rta-rsa2048-1","namespace":"infisical-system"},"namespace":"infisical-system","name":"skynet-infisical-rta-rsa2048-1","reconcileID":"bfb7cad9-d867-45b5-b3a3-0139e731b7a6"}
|
||||||
|
```
|
||||||
|
This indicates that the `CertificateRequest` has been created, but `cert-manager` has not yet approved it. This typically occurs because a necessary approver policy is missing. Refer to the documentation above to create an approver policy.
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { usePopUp } from "@app/hooks/usePopUp";
|
|||||||
|
|
||||||
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
|
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
|
||||||
import { CaModal } from "../CertificateAuthoritiesPage/components/CaModal";
|
import { CaModal } from "../CertificateAuthoritiesPage/components/CaModal";
|
||||||
import { CertificateTemplatesSection } from "../CertificatesPage/components/CertificateTemplatesSection";
|
|
||||||
import {
|
import {
|
||||||
CaCertificatesSection,
|
CaCertificatesSection,
|
||||||
CaCrlsSection,
|
CaCrlsSection,
|
||||||
@@ -126,7 +125,6 @@ const Page = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<CaCertificatesSection caId={data.id} />
|
<CaCertificatesSection caId={data.id} />
|
||||||
<CertificateTemplatesSection caId={data.id} />
|
|
||||||
<CaCrlsSection caId={data.id} />
|
<CaCrlsSection caId={data.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useState } from "react";
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
faCertificate,
|
faCertificate,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faPencil,
|
faPencil,
|
||||||
@@ -107,15 +106,6 @@ export const PkiTemplateListPage = () => {
|
|||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Templates</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Templates</p>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<a target="_blank" rel="noopener noreferrer">
|
|
||||||
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
|
||||||
Documentation{" "}
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faArrowUpRightFromSquare}
|
|
||||||
className="mb-[0.06rem] ml-1 text-xs"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionPkiTemplateActions.Create}
|
I={ProjectPermissionPkiTemplateActions.Create}
|
||||||
a={ProjectPermissionSub.CertificateTemplates}
|
a={ProjectPermissionSub.CertificateTemplates}
|
||||||
|
|||||||
Reference in New Issue
Block a user