about summary refs log tree commit diff
path: root/corp/ops
diff options
context:
space:
mode:
Diffstat (limited to 'corp/ops')
-rw-r--r--corp/ops/.envrc4
-rw-r--r--corp/ops/.gitignore4
-rw-r--r--corp/ops/default.nix46
-rw-r--r--corp/ops/modules/.skip-tree1
-rw-r--r--corp/ops/modules/yandex-cloud.nix79
-rw-r--r--corp/ops/yandex/creds.fish5
-rw-r--r--corp/ops/yandex/encrypted-state-secret.keybin0 -> 121 bytes
-rw-r--r--corp/ops/yandex/main.tf70
-rw-r--r--corp/ops/yandex/rih.tf69
9 files changed, 278 insertions, 0 deletions
diff --git a/corp/ops/.envrc b/corp/ops/.envrc
new file mode 100644
index 000000000000..26049cf426a4
--- /dev/null
+++ b/corp/ops/.envrc
@@ -0,0 +1,4 @@
+out=$(nix-build ../.. -A corp.ops.deps --out-link ../../.gcroots/corp-deps)
+PATH_add "$out/bin"
+
+watch_file default.nix
diff --git a/corp/ops/.gitignore b/corp/ops/.gitignore
new file mode 100644
index 000000000000..5def054d764c
--- /dev/null
+++ b/corp/ops/.gitignore
@@ -0,0 +1,4 @@
+.terraform
+.terraform.lock.hcl
+terraform.tfstate
+terraform.tfstate.backup
diff --git a/corp/ops/default.nix b/corp/ops/default.nix
new file mode 100644
index 000000000000..59d63bd5101b
--- /dev/null
+++ b/corp/ops/default.nix
@@ -0,0 +1,46 @@
+{ depot, lib, pkgs, ... }:
+
+depot.nix.readTree.drvTargets rec {
+  # Provide a Terraform wrapper with Yandex Cloud support.
+  terraform = pkgs.terraform.withPlugins (p: [
+    p.yandex
+  ]);
+
+  validate = depot.tools.checks.validateTerraform {
+    inherit terraform;
+    name = "corp";
+    src = lib.cleanSource ./.;
+  };
+
+  # Yandex Cloud CLI
+  yc-cli = pkgs.stdenv.mkDerivation rec {
+    pname = "yc-cli";
+    version = "0.106.0";
+
+    src = pkgs.fetchurl {
+      url = "https://storage.yandexcloud.net/yandexcloud-yc/release/${version}/linux/amd64/yc";
+      sha256 = "sha256:1f7fq9rlihz91ld1vdjj9vq9ssq1ls031jin4zisxv75rcdpslh3";
+    };
+
+    phases = [ "installPhase" ];
+    installPhase = "install -D $src $out/bin/yc";
+  };
+
+  deps = depot.tools.depot-deps.overrideDeps {
+    tf-yandex = {
+      attr = "corp.ops.terraform";
+      cmd = "terraform";
+    };
+
+    yc.attr = "corp.ops.yc-cli";
+  };
+
+  # Base image for Yandex VMs.
+  yandex-base-image = (depot.third_party.nixos {
+    configuration = { ... }: {
+      imports = [
+        (depot.path.origSrc + ("/corp/ops/modules/yandex-cloud.nix"))
+      ];
+    };
+  }).config.system.build.yandexCloudImage;
+}
diff --git a/corp/ops/modules/.skip-tree b/corp/ops/modules/.skip-tree
new file mode 100644
index 000000000000..a6f528167f00
--- /dev/null
+++ b/corp/ops/modules/.skip-tree
@@ -0,0 +1 @@
+Only NixOS modules here.
diff --git a/corp/ops/modules/yandex-cloud.nix b/corp/ops/modules/yandex-cloud.nix
new file mode 100644
index 000000000000..cca81bc0ca5e
--- /dev/null
+++ b/corp/ops/modules/yandex-cloud.nix
@@ -0,0 +1,79 @@
+# Profile for virtual machines on Yandex Cloud, intended for disk
+# images.
+#
+# https://cloud.yandex.com/en/docs/compute/operations/image-create/custom-image
+#
+# TODO(tazjin): Upstream to nixpkgs once it works well.
+{ config, lib, pkgs, modulesPath, ... }:
+
+let
+  cfg = config.virtualisation.yandexCloud;
+
+  # Kernel modules required for interacting with the hypervisor. These
+  # must be available during stage 1 boot and during normal operation,
+  # as disks and network do not work without them.
+  modules = [
+    "virtio-net"
+    "virtio-blk"
+    "virtio-pci"
+    "virtiofs"
+  ];
+in
+{
+  imports = [
+    "${modulesPath}/profiles/headless.nix"
+  ];
+
+  options = {
+    virtualisation.yandexCloud.rootPartitionUuid = with lib; mkOption {
+      type = types.str;
+      default = "C55A5EE2-E5FA-485C-B3AE-CC928429AB6B";
+
+      description = ''
+        UUID to use for the root partition of the disk image. Yandex
+        Cloud requires that root partitions are mounted by UUID.
+
+        Most users do not need to set this to a non-default value.
+      '';
+    };
+  };
+
+  config = {
+    fileSystems."/" = {
+      device = "/dev/disk/by-uuid/${lib.toLower cfg.rootPartitionUuid}";
+      fsType = "ext4";
+      autoResize = true;
+    };
+
+    boot = {
+      loader.grub.device = "/dev/vda";
+
+      initrd.kernelModules = modules;
+      kernelModules = modules;
+      kernelParams = [
+        # Enable support for the serial console
+        "console=ttyS0"
+      ];
+
+      growPartition = true;
+    };
+
+    environment.etc.securetty = {
+      text = "ttyS0";
+      mode = "0644";
+    };
+
+    systemd.services."serial-getty@ttyS0".enable = true;
+
+    services.openssh.enable = true;
+    services.cloud-init.enable = true;
+
+    system.build.yandexCloudImage = import (pkgs.path + "/nixos/lib/make-disk-image.nix") {
+      inherit lib config pkgs;
+      additionalSpace = "128M";
+      format = "qcow2";
+      partitionTableType = "legacy+gpt";
+      rootGPUID = cfg.rootPartitionUuid;
+    };
+  };
+}
diff --git a/corp/ops/yandex/creds.fish b/corp/ops/yandex/creds.fish
new file mode 100644
index 000000000000..2985b2880864
--- /dev/null
+++ b/corp/ops/yandex/creds.fish
@@ -0,0 +1,5 @@
+export YC_TOKEN=(yc iam create-token)
+export YC_CLOUD_ID=(yc config get cloud-id)
+export YC_FOLDER_ID=(yc config get folder-id)
+export AWS_ACCESS_KEY_ID="YCAJE6eRLY8Az-9kveNRtz4sh"
+export AWS_SECRET_ACCESS_KEY=(yc kms symmetric-crypto decrypt --name tvl-credentials --cloud-id b1ggu5m1btue982app12 --folder-name default --ciphertext-file encrypted-state-secret.key --plaintext-file /dev/stdout | head -n1)
diff --git a/corp/ops/yandex/encrypted-state-secret.key b/corp/ops/yandex/encrypted-state-secret.key
new file mode 100644
index 000000000000..0d07158f2f01
--- /dev/null
+++ b/corp/ops/yandex/encrypted-state-secret.key
Binary files differdiff --git a/corp/ops/yandex/main.tf b/corp/ops/yandex/main.tf
new file mode 100644
index 000000000000..cd8fa6e4cc67
--- /dev/null
+++ b/corp/ops/yandex/main.tf
@@ -0,0 +1,70 @@
+# Terraform configuration for TVL corp infrastructure (on Yandex
+# Cloud).
+
+terraform {
+  required_providers {
+    yandex = {
+      source = "yandex-cloud/yandex"
+    }
+  }
+
+  # Credentials need to be sourced from creds.fish
+  backend "s3" {
+    endpoint = "storage.yandexcloud.net"
+    bucket   = "su-tvl-terraform-state"
+    region   = "ru-central1"
+    key      = "corp/ops/terraform.tfstate"
+
+    skip_region_validation      = true
+    skip_credentials_validation = true
+  }
+}
+
+provider "yandex" {
+  zone = "ru-central1-b"
+}
+
+locals {
+  tvl_cloud_id  = "b1ggu5m1btue982app12"
+  tvl_folder_id = "b1gmbeqt9o5kbl7rclln"
+  rih_cloud_id  = "b1glccvcqggi2ruibgvt"
+  rih_folder_id = "b1gsavcrsjn059d1sbh9"
+}
+
+# Storage state bucket configuration
+
+resource "yandex_iam_service_account" "tf_state_sa" {
+  folder_id = local.tvl_folder_id
+  name      = "terraform-state"
+}
+
+resource "yandex_resourcemanager_folder_iam_member" "tf_state_sa_storage" {
+  folder_id = local.tvl_folder_id
+  role      = "storage.editor"
+  member    = "serviceAccount:${yandex_iam_service_account.tf_state_sa.id}"
+}
+
+resource "yandex_iam_service_account_static_access_key" "tf_state_sa_key" {
+  service_account_id = yandex_iam_service_account.tf_state_sa.id
+  description        = "Static access key for Terraform state"
+}
+
+resource "yandex_storage_bucket" "tf_state" {
+  access_key = yandex_iam_service_account_static_access_key.tf_state_sa_key.access_key
+  secret_key = yandex_iam_service_account_static_access_key.tf_state_sa_key.secret_key
+  bucket     = "su-tvl-terraform-state"
+}
+
+# Secret management configuration
+
+resource "yandex_kms_symmetric_key" "tvl_credentials_key" {
+  name              = "tvl-credentials"
+  folder_id         = local.tvl_folder_id
+  default_algorithm = "AES_256"
+  rotation_period   = "2160h" # 90 days
+}
+
+resource "yandex_kms_secret_ciphertext" "tf_state_key" {
+  key_id    = yandex_kms_symmetric_key.tvl_credentials_key.id
+  plaintext = yandex_iam_service_account_static_access_key.tf_state_sa_key.secret_key
+}
diff --git a/corp/ops/yandex/rih.tf b/corp/ops/yandex/rih.tf
new file mode 100644
index 000000000000..de51ad738e80
--- /dev/null
+++ b/corp/ops/yandex/rih.tf
@@ -0,0 +1,69 @@
+# Deployment configuration for russiaishiring.com
+#
+# The frontend of the page is served from a storage bucket, the
+# backend runs in a container.
+
+resource "yandex_dns_zone" "russiaishiring_com" {
+  name      = "russiaishiring-com"
+  zone      = "russiaishiring.com."
+  public    = true
+  folder_id = local.rih_folder_id
+}
+
+resource "yandex_iam_service_account" "rih_storage_sa" {
+  name      = "rih-storage-sa"
+  folder_id = local.rih_folder_id
+}
+
+resource "yandex_resourcemanager_folder_iam_member" "rih_sa_storage_editor" {
+  folder_id = local.rih_folder_id
+  role      = "storage.editor"
+  member    = "serviceAccount:${yandex_iam_service_account.rih_storage_sa.id}"
+}
+
+resource "yandex_iam_service_account_static_access_key" "rih_sa_static_key" {
+  service_account_id = yandex_iam_service_account.rih_storage_sa.id
+  description        = "RIH bucket access key"
+}
+
+resource "yandex_storage_bucket" "rih_storage_bucket" {
+  access_key = yandex_iam_service_account_static_access_key.rih_sa_static_key.access_key
+  secret_key = yandex_iam_service_account_static_access_key.rih_sa_static_key.secret_key
+  bucket     = "russiaishiring.com"
+  folder_id  = local.rih_folder_id
+  acl        = "public-read"
+
+  https {
+    certificate_id = yandex_cm_certificate.russiaishiring_com.id
+  }
+
+  website {
+    index_document = "index.html"
+  }
+}
+
+resource "yandex_cm_certificate" "russiaishiring_com" {
+  folder_id = local.rih_folder_id
+  name      = "russiaishiring-com"
+  domains   = ["russiaishiring.com"]
+
+  managed {
+    challenge_type = "DNS_CNAME"
+  }
+}
+
+resource "yandex_dns_recordset" "acme_russiaishiring_com" {
+  zone_id = yandex_dns_zone.russiaishiring_com.id
+  name    = yandex_cm_certificate.russiaishiring_com.challenges[0].dns_name
+  type    = yandex_cm_certificate.russiaishiring_com.challenges[0].dns_type
+  data    = [yandex_cm_certificate.russiaishiring_com.challenges[0].dns_value]
+  ttl     = 60
+}
+
+resource "yandex_dns_recordset" "aname_russiaishiring_com" {
+  zone_id = yandex_dns_zone.russiaishiring_com.id
+  name    = "russiaishiring.com."
+  type    = "ANAME"
+  data    = ["russiaishiring.com.website.yandexcloud.net"]
+  ttl     = 600
+}