morvencao / kube-sidecar-injector

A Kubernetes mutating webhook server that implements sidecar injection
Apache License 2.0
628 stars 455 forks source link

How do you approve the CA in the cluster #47

Open haykharut opened 1 year ago

haykharut commented 1 year ago

Hi!

I have been trying to replicate your code in Python and have reached a point where I managed to :

1) create the certificates and a private key 2) create the webhookconfiguration in the cluster

The problem I have is that the certificate is not recognized:

failed to call webhook: Post "...svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority

Looking at your code, I cannot find how the self-signed certificate is made recognizable to kubernetes. I can see that :

There are 2 CA configs in the script and 2 certificates made. The first is passed to the webhook configuration and the second is used in the webserver together with the private key. I do not quite understand why this is enough for kubernetes to recognize the certificate signer. Isn't there supposed to be a Certificate Signing Request made?

haykharut commented 1 year ago

For reference:

Here is my script that makes the cert + key:

import datetime

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID

def generate_cert(org, dns_names, common_name):
    # Init CA config
    ca = (
        x509.CertificateBuilder(
            issuer_name=x509.Name(
                [x509.NameAttribute(x509.oid.NameOID.ORGANIZATION_NAME, org)]
            ),
            subject_name=x509.Name(
                [x509.NameAttribute(x509.oid.NameOID.ORGANIZATION_NAME, org)]
            ),
            serial_number=2022,
            not_valid_before=datetime.datetime.utcnow(),
            not_valid_after=datetime.datetime.utcnow() + datetime.timedelta(days=365),
            public_key=rsa.generate_private_key(
                public_exponent=65537, key_size=4096
            ).public_key(),
        )
        .add_extension(
            x509.BasicConstraints(ca=True, path_length=None),
            critical=True,
        )
        .add_extension(
            x509.KeyUsage(
                digital_signature=True,
                content_commitment=False,
                key_encipherment=False,
                data_encipherment=False,
                key_agreement=False,
                key_cert_sign=True,
                crl_sign=False,
                encipher_only=False,
                decipher_only=False,
            ),
            critical=True,
        )
        .add_extension(
            x509.ExtendedKeyUsage(
                [
                    x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH,
                    x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
                ]
            ),
            critical=False,
        )
    )

    # Sign CA and generate public key in PEM format
    ca_pem = ca.sign(
            private_key=rsa.generate_private_key(
                public_exponent=65537,
                key_size=4096,
            ),
            algorithm=hashes.SHA256(),
        ).public_bytes(encoding=serialization.Encoding.PEM)

    # generate new private key
    new_private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=4096,
    )

    # New certificate config
    new_cert = x509.CertificateBuilder(
        serial_number=2022,
        issuer_name=x509.Name(
            [
                x509.NameAttribute(NameOID.COMMON_NAME, common_name),
                x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
            ]
        ),
        subject_name=x509.Name(
            [
                x509.NameAttribute(NameOID.COMMON_NAME, common_name),
                x509.NameAttribute(NameOID.ORGANIZATION_NAME, org),
            ]
        ),
        not_valid_before=datetime.datetime.utcnow(),
        not_valid_after=datetime.datetime.utcnow() + datetime.timedelta(days=365),
        public_key=new_private_key.public_key(),
    )
    new_cert = new_cert.add_extension(
        x509.SubjectAlternativeName([x509.DNSName(dns_name) for dns_name in dns_names]),
        critical=False,
    )
    new_cert = new_cert.add_extension(
        x509.KeyUsage(
            digital_signature=True,
            key_encipherment=False,
            content_commitment=False,
            key_agreement=False,
            data_encipherment=False,
            key_cert_sign=True,
            crl_sign=False,
            encipher_only=False,
            decipher_only=False,
        ),
        critical=True,
    )

    # sign the new certificate and encode to PEM
    new_cert_pem = new_cert.sign(
        private_key=new_private_key,
        algorithm=hashes.SHA256(),
        backend=default_backend(),
    ).public_bytes(serialization.Encoding.PEM)

    # new private key PEM encoded
    new_private_key_pem = new_private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )

    return ca_pem, new_cert_pem, new_private_key_pem

And here is where my webhook config

def create_or_update_mutating_webhook_configuration(
    ca_pem, webhook_service, webhook_namespace
):
    # Initialize the kube client
    clientset = client.ApiClient(config.load_incluster_config())
    # Create or update the mutatingwebhookconfiguration
    apiclient = AdmissionregistrationV1Api(api_client=clientset)

    mutating_webhook_config = client.V1MutatingWebhookConfiguration(
        metadata=client.V1ObjectMeta(name=webhook_config_name),
        webhooks=[
            client.V1MutatingWebhook(
                name="mywebhook",
                admission_review_versions=["v1", "v1beta1"],
                side_effects="None",
                client_config=client.AdmissionregistrationV1WebhookClientConfig(
                    ca_bundle=ca_pem,  # self-generated CA for the webhook
                    service=client.AdmissionregistrationV1ServiceReference(
                        name=webhook_service,
                        namespace=webhook_namespace,
                        path="/mutate",
                    ),
                ),
                rules=[
                    client.V1RuleWithOperations(
                        operations=["CREATE", "UPDATE"],
                        api_groups=[""],
                        api_versions=["v1"],
                        resources=["pods"],
                    ),
                ],
                failure_policy="Fail",
            )
        ],
    )