diff --git a/.helm/templates/configs/cm.yaml b/.helm/templates/configs/cm.yaml index 2519e31..f03be6e 100644 --- a/.helm/templates/configs/cm.yaml +++ b/.helm/templates/configs/cm.yaml @@ -9,14 +9,3 @@ data: {{ $conf_key }}: {{ $conf_value | quote }} {{- end}} DB_HOST: {{ .Release.Name }}-postgresql ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "sunflower.fullname" . }}-docs - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} -data: -{{- range $conf_key,$conf_value := .Values.docs.configs }} - {{ $conf_key }}: {{ $conf_value | quote }} -{{- end}} diff --git a/.helm/templates/configs/secret.yaml b/.helm/templates/configs/secret.yaml index 701d42d..fee5a55 100644 --- a/.helm/templates/configs/secret.yaml +++ b/.helm/templates/configs/secret.yaml @@ -5,12 +5,3 @@ metadata: labels: {{- include "sunflower.labels.application" . | nindent 4 }} data: - ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "sunflower.fullname" . }}-docs - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} -data: diff --git a/.helm/templates/docs/deployment.yaml b/.helm/templates/docs/deployment.yaml deleted file mode 100644 index 0aa53fe..0000000 --- a/.helm/templates/docs/deployment.yaml +++ /dev/null @@ -1,66 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "sunflower.fullname" . }}-docs - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} -spec: - replicas: {{ .Values.docs.replicaCount }} - selector: - matchLabels: - {{- include "sunflower.selectorLabels.docs" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "sunflower.selectorLabels.docs" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "sunflower.serviceAccountName.docs" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - envFrom: - - configMapRef: - name: {{ include "sunflower.fullname" . }}-docs - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - {{- if .Values.werf }} - image: "{{ index .Values.werf.image "docs"}}" - {{- else }} - image: "{{ .Values.docs.image.repository }}:{{ .Values.docs.image.tag | default .Chart.AppVersion }}" - {{- end }} - imagePullPolicy: {{ .Values.docs.image.pullPolicy }} - ports: - - name: http - containerPort: 8000 - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http - resources: - {{- toYaml .Values.docs.resources | nindent 12 }} - {{- with .Values.docs.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.docs.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.docs.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/.helm/templates/docs/ingress-werf.yaml b/.helm/templates/docs/ingress-werf.yaml deleted file mode 100644 index 42011dc..0000000 --- a/.helm/templates/docs/ingress-werf.yaml +++ /dev/null @@ -1,53 +0,0 @@ -{{- if .Values.werf -}} -{{- $fullName := include "sunflower.fullname" . -}} -{{- $svcPort := .Values.docs.service.port -}} -{{- if and .Values.docs.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.docs.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.docs.ingress.annotations "kubernetes.io/ingress.class" .Values.docs.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }}-docs - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} - {{- with .Values.docs.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - ingressClassName: nginx-external - tls: - - hosts: - - "api{{ .Values.envUrl }}" - secretName: fakesecret - rules: - {{- range .Values.docs.ingress.hosts }} - - host: "api{{ $.Values.envUrl }}" - http: - paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }}-docs - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }}-docs - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} diff --git a/.helm/templates/docs/ingress.yaml b/.helm/templates/docs/ingress.yaml deleted file mode 100644 index 95c73fb..0000000 --- a/.helm/templates/docs/ingress.yaml +++ /dev/null @@ -1,61 +0,0 @@ -{{- if .Values.docs.ingress.enabled -}} -{{- $fullName := include "sunflower.fullname" . -}} -{{- $svcPort := .Values.docs.service.port -}} -{{- if and .Values.docs.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.docs.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.docs.ingress.annotations "kubernetes.io/ingress.class" .Values.docs.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }}-docs - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} - {{- with .Values.docs.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.docs.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.docs.ingress.className }} - {{- end }} - {{- if .Values.docs.ingress.tls }} - tls: - {{- range .Values.docs.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.docs.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }}-docs - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }}-docs - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/.helm/templates/docs/service.yaml b/.helm/templates/docs/service.yaml deleted file mode 100644 index 5ba6971..0000000 --- a/.helm/templates/docs/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "sunflower.fullname" . }}-docs - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} -spec: - type: {{ .Values.docs.service.type }} - ports: - - port: {{ .Values.docs.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "sunflower.selectorLabels.docs" . | nindent 4 }} diff --git a/.helm/templates/docs/serviceaccount.yaml b/.helm/templates/docs/serviceaccount.yaml deleted file mode 100644 index b95af7d..0000000 --- a/.helm/templates/docs/serviceaccount.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.docs.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "sunflower.serviceAccountName.docs" . }} - labels: - {{- include "sunflower.labels.docs" . | nindent 4 }} - {{- with .Values.docs.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- if .Values.dockerconfigjson }} -imagePullSecrets: - - name: {{ include "sunflower.fullname" . }}-registrysecret -{{- end }} -{{- end }} diff --git a/.helm/values.yaml b/.helm/values.yaml index acf9273..db03151 100644 --- a/.helm/values.yaml +++ b/.helm/values.yaml @@ -147,80 +147,3 @@ application: tolerations: [] affinity: {} - - -docs: - nodeSelector: {} - - tolerations: [] - - affinity: {} - - resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - configs: {} - # ENV_NAME: eaxmple - - replicaCount: 1 - - image: - repository: ghcr.io/amazeit/sunflower/sunflower - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: - - imagePullSecrets: [] - nameOverride: "" - fullnameOverride: "" - - serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - - podAnnotations: {} - - podSecurityContext: {} - # fsGroup: 2000 - - securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - - service: - type: ClusterIP - port: 80 - - ingress: - enabled: false - className: "" - annotations: - external-dns.kubernetes.io/class: cloudflare - # nginx.ingress.kubernetes.io/rewrite-target: /$2 - # nginx.ingress.kubernetes.io/use-regex: "true" - hosts: - - host: api.example.local - paths: - - path: /docs/ - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local diff --git a/ocsp/views.py b/ocsp/views.py index c272895..a574c0c 100644 --- a/ocsp/views.py +++ b/ocsp/views.py @@ -1,7 +1,7 @@ from cryptography.x509 import ocsp from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt -from django_filters.rest_framework import DjangoFilterBackend +from django_filters import rest_framework from rest_framework import permissions, viewsets from rest_framework.response import Response @@ -124,7 +124,7 @@ class SourceViewSet(viewsets.ModelViewSet): queryset = Source.objects.all() serializer_class = SourceSerializer permission_classes = [permissions.IsAuthenticated] - filter_backends = [DjangoFilterBackend] + filter_backends = [rest_framework.DjangoFilterBackend] filterset_fields = ["name", "host", "addr"] def create(self, request, *args, **kwargs): @@ -185,10 +185,29 @@ def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) +class RequestLogFilterSet(rest_framework.FilterSet): + date = rest_framework.DateFromToRangeFilter() + cert = rest_framework.ModelChoiceFilter( + queryset=Certificate.objects.all(), null_label="null" + ) + + class Meta: + model = RequestLog + fields = [ + "date", + "cert", + "host", + "addr", + "result", + ] + + class RequestLogViewSet(viewsets.ModelViewSet): queryset = RequestLog.objects.all() serializer_class = RequestLogSerializer permission_classes = [permissions.IsAuthenticated] + filter_backends = [rest_framework.DjangoFilterBackend] + filterset_class = RequestLogFilterSet http_method_names = ["get"] diff --git a/sunflower/urls.py b/sunflower/urls.py index 23c96eb..4d1416e 100644 --- a/sunflower/urls.py +++ b/sunflower/urls.py @@ -99,6 +99,6 @@ name="redoc", ), # Non versioned views - path("crl/.", crl_view, name="crl"), + path("crl/.", crl_view, name="crl"), path("ocsp/", ocsp_view, name="ocsp"), ] diff --git a/werf.yaml b/werf.yaml index cdaeb9e..2746e13 100644 --- a/werf.yaml +++ b/werf.yaml @@ -4,7 +4,3 @@ project: sunflower image: application dockerfile: Dockerfile context: . ---- -image: docs -dockerfile: docs/Dockerfile -context: . diff --git a/x509/migrations/0011_remove_csr_slug.py b/x509/migrations/0011_remove_csr_slug.py new file mode 100644 index 0000000..ed6294e --- /dev/null +++ b/x509/migrations/0011_remove_csr_slug.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.7 on 2024-01-10 15:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("x509", "0010_csr_extended_key_usage_csr_key_usage"), + ] + + operations = [ + migrations.RemoveField( + model_name="csr", + name="slug", + ), + ] diff --git a/x509/models.py b/x509/models.py index 3c2b6e8..fafe0bf 100644 --- a/x509/models.py +++ b/x509/models.py @@ -4,7 +4,6 @@ from cryptography.hazmat.primitives.asymmetric import dsa, rsa from django.conf import settings from django.db import models -from django.utils.text import slugify from utils.crypto import ( REVOCATION_REASONS, @@ -115,9 +114,6 @@ class CSR(models.Model): key = models.ForeignKey(to=Key, on_delete=models.RESTRICT) name = models.CharField(verbose_name="Internal name", max_length=255) - slug = models.SlugField( - verbose_name="Slug", max_length=255, unique=True, blank=True - ) body = models.TextField(verbose_name="CSR", blank=True) params = models.JSONField(verbose_name="Certificate params", blank=True) @@ -142,8 +138,6 @@ def __str__(self) -> str: return self.name def save(self, *args, **kwargs) -> None: - self.slug = slugify(self.name, allow_unicode=True) - data: dict = self.params data.update({"ca": self.ca, "path_length": self.path_length}) @@ -309,7 +303,7 @@ def __str__(self) -> str: def save(self, *args, **kwargs) -> None: if self._state.adding is True: crl_object = make_crl( - self.ca.as_object(), self.ca.csr.key.private_as_object() + self.ca.as_object(), self.ca.key.private_as_object() ) else: @@ -323,7 +317,7 @@ def save(self, *args, **kwargs) -> None: crl_object = make_crl( self.ca.as_object(), - self.ca.csr.key.private_as_object(), + self.ca.key.private_as_object(), revoked_certs, ) diff --git a/x509/views.py b/x509/views.py index 3766452..b565ccc 100644 --- a/x509/views.py +++ b/x509/views.py @@ -1,8 +1,8 @@ import uuid from cryptography.hazmat.primitives.asymmetric import dsa, rsa -from django.http import HttpResponse -from django_filters.rest_framework import DjangoFilterBackend +from django.http import HttpResponse, HttpResponseNotFound +from django_filters import rest_framework from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema from rest_framework import ( @@ -39,13 +39,24 @@ } +class KeyFilterSet(rest_framework.FilterSet): + created_at = rest_framework.DateFromToRangeFilter() + + class Meta: + model = Key + fields = ["algo", "length", "used", "fingerprint", "created_at"] + + class KeyViewSet(viewsets.ModelViewSet): queryset = Key.objects.all() serializer_class = KeySerializer permission_classes = [permissions.IsAuthenticated] - filter_backends = [DjangoFilterBackend, filters.SearchFilter] - filterset_fields = ["algo", "length", "used", "fingerprint"] - search_fields = ["name"] + filter_backends = [ + rest_framework.DjangoFilterBackend, + filters.SearchFilter, + ] + filterset_class = KeyFilterSet + search_fields = ["name", "fingerprint"] def create(self, request, *args, **kwargs): instance = None @@ -155,12 +166,23 @@ def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) +class CSRFilterSet(rest_framework.FilterSet): + created_at = rest_framework.DateFromToRangeFilter() + + class Meta: + model = CSR + fields = ["name", "signed", "ca", "path_length", "created_at"] + + class CSRViewSet(viewsets.ModelViewSet): queryset = CSR.objects.all() serializer_class = CSRSerializer permission_classes = [permissions.IsAuthenticated] - filter_backends = [DjangoFilterBackend, filters.SearchFilter] - filterset_fields = ["name", "signed", "ca", "path_length"] + filter_backends = [ + rest_framework.DjangoFilterBackend, + filters.SearchFilter, + ] + filterset_class = CSRFilterSet search_fields = ["name"] def create(self, request, *args, **kwargs): @@ -269,26 +291,40 @@ def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) +class CertificateFilterSet(rest_framework.FilterSet): + created_at = rest_framework.DateFromToRangeFilter() + revoked_at = rest_framework.DateFromToRangeFilter() + + class Meta: + model = Certificate + fields = [ + "sn", + "parent", + "imported", + "revoked", + "revocation_reason", + "fingerprint", + "created_at", + "revoked_at", + ] + + class CertificateViewSet(viewsets.ModelViewSet): queryset = Certificate.objects.all() serializer_class = CertificateSerialiser permission_classes = [permissions.IsAuthenticated] - filter_backends = [DjangoFilterBackend, filters.SearchFilter] - filterset_fields = [ - "sn", - "parent", - "imported", - "revoked", - "revocation_reason", - "fingerprint", + filter_backends = [ + rest_framework.DjangoFilterBackend, + filters.SearchFilter, ] - search_fields = ["csr__name"] + filterset_class = CertificateFilterSet + search_fields = ["csr__name", "fingerprint"] http_method_names = ["get", "post", "put"] def create(self, request, *args, **kwargs): serializer = self.serializer_class( - data=request.data, context={'request': request} + data=request.data, context={"request": request} ) if serializer.is_valid(): instance = serializer.save() @@ -401,8 +437,16 @@ def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) -def crl_view(request, ca_slug, format: str = "crl"): - crl = CRL.objects.filter(ca__csr__slug=ca_slug).first() +def crl_view(request, ca_sn, format: str = "crl"): + cert = Certificate.objects.get(sn=ca_sn) + if not cert.is_ca: + return HttpResponseNotFound("CA not found") + + crl = CRL.objects.filter(ca=cert).first() + if not crl: # Sad but true + crl = CRL(ca=cert) + crl.save() + if format == "crt": return HttpResponse(crl.as_der()) else: @@ -500,6 +544,10 @@ def post(self, request): ) cert.save() + if cert.is_ca: + crl = CRL(ca=cert) + crl.save() + return Response(CertificateSerialiser(instance=cert).data) except Exception as e: