diff --git a/docs/dev/diagrams/Dockerfile b/docs/dev/diagrams/Dockerfile new file mode 100644 index 000000000..fb3d8249c --- /dev/null +++ b/docs/dev/diagrams/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3-alpine + +RUN apk add \ + graphviz \ + font-noto \ + font-noto-emoji \ + && pip install diagrams + +ARG USER=diagrams + +# add new user +RUN adduser -D $USER + +USER $USER +WORKDIR /tmp + +# docker run -v $(pwd):/tmp/ diagram.py +ENTRYPOINT ["python"] \ No newline at end of file diff --git a/docs/dev/diagrams/Makefile b/docs/dev/diagrams/Makefile new file mode 100644 index 000000000..80e49dafd --- /dev/null +++ b/docs/dev/diagrams/Makefile @@ -0,0 +1,9 @@ +IMAGE_NAME := diagrams + +run: cloud_main_connectivity_profile_state_controller.png cloud_main_connectivity_azure_network.png cloud_main_connectivity_egress_gateway.png + +%.png: %.py build + docker run -v $$(pwd)/:/tmp/ $(IMAGE_NAME) $< + +build: Dockerfile + docker build . -t $(IMAGE_NAME) \ No newline at end of file diff --git a/docs/dev/diagrams/cloud_main_connectivity_azure_network.png b/docs/dev/diagrams/cloud_main_connectivity_azure_network.png new file mode 100644 index 000000000..e7de1801d Binary files /dev/null and b/docs/dev/diagrams/cloud_main_connectivity_azure_network.png differ diff --git a/docs/dev/diagrams/cloud_main_connectivity_azure_network.py b/docs/dev/diagrams/cloud_main_connectivity_azure_network.py new file mode 100644 index 000000000..ae5540946 --- /dev/null +++ b/docs/dev/diagrams/cloud_main_connectivity_azure_network.py @@ -0,0 +1,50 @@ +import os +import profile + +from diagrams import Cluster, Diagram, Edge + +from diagrams.custom import Custom + +from diagrams.azure.network import Firewall +from diagrams.azure.network import RouteTables +from diagrams.azure.network import VirtualNetworks +from diagrams.azure.network import Subnets +from diagrams.azure.network import RouteFilters + +def myself() -> str: + f = os.path.basename(__file__) + no_ext = ".".join(f.split(".")[:-1]) + return no_ext + + +with Diagram(myself(), show=False): + with Cluster("Cloud Main"): + pass + with Cluster("Advanced Analytics Workspace"): + with Cluster("aaw-prod-aks-vnet"): + azure_aks_vnet = VirtualNetworks("azurerm_virtual_network aks") + azure_aks_route_table = RouteTables("azurerm_route_table network") + azure_aks_route_rule = RouteFilters("azurerm_route network_default") + with Cluster("azurerm_subnet cloud-main-system"): + azure_cloudmain_subnet = Subnets("azurerm_subnet cloud-main-system") + non_stc_employee_nb = Custom("non-employee-nb", icon_path="icons/jupyter.png") + with Cluster("azurerm_subnet cloud-main-system"): + azure_cloudmain_subnet = Subnets("azurerm_subnet cloud-main-system") + + with Cluster("aaw-prod-hub-vnet"): + azure_hub_vnet = VirtualNetworks("azurerm_virtual_network hub") + with Cluster("azurerm_subnet hub_firewall"): + azure_cloudmain_subnet = Subnets("azurerm_subnet hub_firewall") + azure_hub_firewall = Firewall("azurerm_firewall firewall") + + + # There is a network peering between aks vnet and aaw-hub vnet + azure_aks_vnet - Edge(style="dashed", color="black", label="azurerm_virtual_network_peering") - azure_hub_vnet + + # There is a route table assocaition between the cloud-main-system subnet and the + # network route table in the aaw-prod-aks-vnet + azure_cloudmain_subnet >> Edge(style="dashed", color="black", label="azurerm_subnet_rt_association") >> azure_aks_route_table + + # The aaw-prod-aks-vnet route table has a route allowing + # The route table in the aaw-prod-aks-vnet routes all outgoing traffic to the AAW hub firewall + azure_aks_route_table >> Edge(style="dashed", color="green", label="next hop") >> azure_hub_firewall diff --git a/docs/dev/diagrams/cloud_main_connectivity_egress_gateway.png b/docs/dev/diagrams/cloud_main_connectivity_egress_gateway.png new file mode 100644 index 000000000..507a8d89a Binary files /dev/null and b/docs/dev/diagrams/cloud_main_connectivity_egress_gateway.png differ diff --git a/docs/dev/diagrams/cloud_main_connectivity_egress_gateway.py b/docs/dev/diagrams/cloud_main_connectivity_egress_gateway.py new file mode 100644 index 000000000..70c9be17e --- /dev/null +++ b/docs/dev/diagrams/cloud_main_connectivity_egress_gateway.py @@ -0,0 +1,75 @@ +import os + +from diagrams import Cluster, Diagram, Edge +from diagrams.azure.compute import AKS +from diagrams.custom import Custom +from diagrams.k8s.others import CRD +from diagrams.k8s.compute import Deployment +from diagrams.k8s.group import NS +from diagrams.k8s.network import NetworkPolicy +from diagrams.k8s.rbac import RoleBinding, Role, ServiceAccount +from diagrams.k8s.podconfig import Secret +from diagrams.onprem.compute import Server + + +def myself() -> str: + f = os.path.basename(__file__) + no_ext = ".".join(f.split(".")[:-1]) + return no_ext + + +with Diagram(myself(), show=False): + cnn_site = Server("edition.cnn.com") + with Cluster("aaw-dev-cc-00"): + aaw_dev_cc_00 = AKS("aaw-dev-cc-00") + with Cluster("istio-operator-system"): + istio_operator_system_ns = NS("istio-operator-system") + istio_operator_controller = Deployment("istio-operator") + with Cluster("istio-system"): + istio_system_ns = NS("istio-system") + istio_daemon = Deployment("istiod") + with Cluster("cloud-main-system"): + cloud_main_system_ns = NS("cloud-main-system") + cnn_egress_gateway_debug = Deployment("cnn-egress-gateway-debug") + cnn_egress_gateway_debug_sa = ServiceAccount("cnn-egress-gateway-debug-sa") + cnn_egress_gateway_debug_sa_token = Secret("cnn-egress-gateway-debug-sa-token") + istio_operator = CRD("cnn-egress-gateway-debug") + # rbac + allow_get_secrets_role = Role("allow-get-secrets") + allow_get_secrets_rb = RoleBinding("allow-get-secrets") + # network policy + allow_ingress_from_collin_brown = NetworkPolicy("allow-ingress-from-stc-employee") + # istio + cnn_gateway = Custom("cnn-gateway", icon_path="icons/istio.png") + with Cluster("stc-employee"): + collin_brown_ns = NS("stc-employee") + jupyter_notebook = Custom("test-employee-notebook", icon_path="icons/jupyter.png") + # network policy + allow_egress_to_cloud_main = NetworkPolicy("allow-egress-to-cloud-main") + # istio + cnn_virtual_service = Custom("cnn-virtual-service", icon_path="icons/istio.png") + cnn_destination_rule = Custom("cnn-destination-rule", icon_path="icons/istio.png") + cnn_service_entry = Custom("cnn-service-entry", icon_path="icons/istio.png") + + + # cnn_egress_gateway_debug pod runs under cnn_egress_gateway_debug_sa service account + cnn_egress_gateway_debug >> Edge(style="dashed", color="black", label="runs under") >> cnn_egress_gateway_debug_sa + # Rolebinding allows pods running under cnn_egress_gateway_debug service account to get secrets in the cloud-main-system namespace + cnn_egress_gateway_debug_sa - Edge(style="dashed", color="black") - allow_get_secrets_rb - Edge(style="dashed", color="black") - allow_get_secrets_role + + # Network policies are set up in both directions to allow ingress from stc-employee and egress to cloud-main-system + allow_egress_to_cloud_main - Edge(style="dashed", color="green", label="allows") - cloud_main_system_ns + allow_ingress_from_collin_brown - Edge(style="dashed", color="green", label="allows") - collin_brown_ns + + # VirtualService routes requests to edition.cnn.com to the egress gateway in `cloud-main-system` + jupyter_notebook >> Edge(color="green", label="routes") >> cnn_virtual_service + # Virtual services describe **how you route your traffic to a given destination**, while destination rules + # describe what happens to traffic for that destination. In our case, the destination rule + # simply has the traffic routed directly to the egress gateway. + cnn_virtual_service >> Edge(color="green",label="routes") >> cnn_destination_rule + cnn_destination_rule >> Edge(color="green",label="routes") >> cnn_egress_gateway_debug + # The ServiceEntry adds the external service (edition.cnn.com) to Istio's service mesh so that + # routing logic can be applied to the external service. + cnn_egress_gateway_debug >> Edge(color="green",label="routes") >> cnn_service_entry + cnn_service_entry >> Edge(color="green",label="routes") >> cnn_site + diff --git a/docs/dev/diagrams/cloud_main_connectivity_profile_state_controller.png b/docs/dev/diagrams/cloud_main_connectivity_profile_state_controller.png new file mode 100644 index 000000000..e7ec4a23f Binary files /dev/null and b/docs/dev/diagrams/cloud_main_connectivity_profile_state_controller.png differ diff --git a/docs/dev/diagrams/cloud_main_connectivity_profile_state_controller.py b/docs/dev/diagrams/cloud_main_connectivity_profile_state_controller.py new file mode 100644 index 000000000..4d03773db --- /dev/null +++ b/docs/dev/diagrams/cloud_main_connectivity_profile_state_controller.py @@ -0,0 +1,53 @@ +import os +import profile + +from diagrams import Cluster, Diagram, Edge +from diagrams.azure.compute import AKS +from diagrams.custom import Custom +from diagrams.k8s.compute import Deployment, Pod, StatefulSet +from diagrams.k8s.network import NetworkPolicy, Service +from diagrams.k8s.group import NS +from diagrams.k8s.rbac import RoleBinding + + +def myself() -> str: + f = os.path.basename(__file__) + no_ext = ".".join(f.split(".")[:-1]) + return no_ext + + +with Diagram(myself(), show=False): + with Cluster("Advanced Analytics Workspace"): + with Cluster("aaw-prod-cc-00"): + aaw_prod_cc_00 = AKS("aaw-prod-cc-00") + with Cluster("non-employee-namespace"): + non_stc_employee_kf_profile = Custom("non-stc-employee-kf-profile", icon_path="icons/kubeflow.png") + non_stc_employee_ns = NS("non-stc-employee") + john_doe_rb_1 = RoleBinding("john.doe@cloud.statcan.ca") + alice_rb = RoleBinding("alice@external.ca") + with Cluster("employee-namespace"): + stc_employee_kf_profile = Custom("stc-employee-kf-profile", icon_path="icons/kubeflow.png") + stc_employee_ns = NS("non-stc-employee") + jane_doe_rb = RoleBinding("jane.doe@statcan.gc.ca") + john_doe_rb_2 = RoleBinding("john.doe@cloud.statcan.ca") + with Cluster("daaas-system"): + profile_state_controller = Pod("profile-state-controller") + daaas_system_ns = NS("daaas-system") + + # Profile State Controller watches rolebindings associated with each + # Kubeflow profile to detect whether emails from a non-statcan domain + # are present. + john_doe_rb_1 >> Edge(style="dashed", color="black") >> profile_state_controller + john_doe_rb_2 >> Edge(style="dashed", color="black") >> profile_state_controller + alice_rb >> Edge(style="dashed", color="black") >> profile_state_controller + jane_doe_rb >> Edge(style="dashed", color="black") >> profile_state_controller + + # Non-employee Kubeflow profiles and namespaces get the label + # `state.aaw.statcan.gc.ca/non-employee-users=true` + profile_state_controller >> Edge(style="dashed", color="red", label="non-employee-users=true") >> non_stc_employee_kf_profile + profile_state_controller >> Edge(style="dashed", color="red", label="non-employee-users=true") >> non_stc_employee_ns + + # Employee Kubeflow profiles and namespaces get the label + # `state.aaw.statcan.gc.ca/non-employee-users=false` + profile_state_controller >> Edge(style="dashed", color="red", label="non-employee-users=false") >> stc_employee_kf_profile + profile_state_controller >> Edge(style="dashed", color="red", label="non-employee-users=false") >> stc_employee_ns diff --git a/docs/dev/diagrams/icons/istio.png b/docs/dev/diagrams/icons/istio.png new file mode 100644 index 000000000..f68f7cb58 Binary files /dev/null and b/docs/dev/diagrams/icons/istio.png differ diff --git a/docs/dev/diagrams/icons/jupyter.png b/docs/dev/diagrams/icons/jupyter.png new file mode 100644 index 000000000..60672c537 Binary files /dev/null and b/docs/dev/diagrams/icons/jupyter.png differ diff --git a/docs/dev/diagrams/icons/kubeflow.png b/docs/dev/diagrams/icons/kubeflow.png new file mode 100644 index 000000000..ca021495e Binary files /dev/null and b/docs/dev/diagrams/icons/kubeflow.png differ diff --git a/docs/dev/features/cloud-main-connectivity.md b/docs/dev/features/cloud-main-connectivity.md new file mode 100644 index 000000000..e832e3d22 --- /dev/null +++ b/docs/dev/features/cloud-main-connectivity.md @@ -0,0 +1,45 @@ +# Cloud Main Connectivity + +Please see the [Cloud Main Connectivity Epic](https://github.com/StatCan/daaas/issues/1097) for more information on the various issues that comprise this feature. + +# Architecture Overview + +## Distinguishing Employees/Non-Employees + +This section covers the component that distinguishes employee-only namespaces from namespaces that contain any non-employee users.[^1] + +![profile-state-controller](../diagrams/cloud_main_connectivity_profile_state_controller.png) + + +## Istio Routing High-Level + +This section covers a high-level overview of how routing of outgoing traffic is handled differently for employee-only vs. non-employee namespaces. + +## Istio Routing Detailed + +This section covers the details of how outgoing traffic from employee-only namespaces are routed through the `cloud-main-system` egress gateway.[^2] + +![istio-routing-egress-gateway](../diagrams/cloud_main_connectivity_egress_gateway.png) + +## Azure Networking + +This section covers the Azure Networking component of the cloud main connectivity implementation.[^3] + +![azure-network-diagram](../diagrams/cloud_main_connectivity_azure_network.png) + +# Associated Repositories + + + +# Feature Deployment + +This section covers the deployment details for the various components involved with the cloud-main connectivity feature. + +## Egress Gateway Deployment + + +# References + +[^1]: Kubeflow icon borrowed from [Kubeflow Github repo](https://github.com/kubeflow/marketing-materials/tree/master/logos/Raster) +[^2]: Istio icons provided by [Istio Media Resources](https://istio.io/latest/about/media-resources/) +[^3]: Jupyterlab icon from [wikimedia-commons](https://commons.wikimedia.org/wiki/File:Jupyter_logo.svg) diff --git a/docs/dev/features/index.md b/docs/dev/features/index.md new file mode 100644 index 000000000..62d6030c8 --- /dev/null +++ b/docs/dev/features/index.md @@ -0,0 +1,10 @@ +# AAW Feature Documentation + +This purpose of this section of the documentation is to cover features that are not appropriate to document in a single repository. For example, certain platform features involve many repositories, so their documentation should be centralized in this documentation section to improve discoverability. + +Feature documentation should include the following information: + +1. A link to the Epic created for the feature (the Epic should have links to all of the sub-issues that were involved in the Epic). +2. Links to all repositories that were involved with the feature along with a short description of the scope of each repository's involvement. +3. Architecture diagrams where appropriate to show how all of the components fit together. Diagrams should be specified as code using the [Python diagrams package](https://diagrams.mingrammer.com/). There is a `Makefile` and a `Dockerfile` under `docs/dev/diagrams`; to create all diagrams, add your diagram name to the `Makefile` and run `make run` in the `docs/dev/diagrams` folder. +4. A short explanation of how the feature works and what the purpose/scope of the feature is. \ No newline at end of file