Пример #1
0
    def get_env(self) -> Dict[str, str]:
        revision = os.environ["K_REVISION"]

        project = self._get_from_metadata("project/project-id")
        zone = self._get_from_metadata("instance/zone")
        region = Path(zone).name[:-2]

        name = f"projects/{project}/locations/{region}/revisions/{revision}"

        cloudrun = self._build("run", "v1alpha1")
        try:
            service = cloudrun.projects().locations().revisions().get(name=name).execute()
        except HttpError as e:
            if e.resp.status == http.client.NOT_FOUND.value:
                raise AutoException("cloud run service not found")
            elif e.resp.status == http.client.UNAUTHORIZED.value:
                raise AutoException("permission denied to fetch cloud run info")

            raise AutoException(f"unable to fetch cloud run service: {e.content}")

        if "spec" not in service or "containers" not in service["spec"] or not service["spec"]["containers"]:
            raise AutoException(f"unable to get env from cloud run")

        env = service["spec"]["containers"][0]["env"]
        return {v["name"]: v["value"] for v in env}
Пример #2
0
def _decrypt_gcm(dek: bytes, ciphertext: bytes) -> bytes:
    # TODO use some constant from crypto library instead of hardcoding here
    nonce_size = 12
    tag_size = 16
    if len(ciphertext) < nonce_size + tag_size:
        raise AutoException("malformed ciphertext")
    nonce, ciphertext, tag = ciphertext[:nonce_size], ciphertext[
        nonce_size:-tag_size], ciphertext[-tag_size:]
    cipher = AES.new(dek, AES.MODE_GCM, nonce=nonce)
    try:
        return cipher.decrypt_and_verify(ciphertext, tag)
    except ValueError:
        raise AutoException("failed to decrypt ciphertext")
Пример #3
0
    def resolve(self, value: str) -> bytes:
        """
        Resolves a berglas reference value to the plain text secret
        """
        if not is_reference(value):
            raise AutoException("not a berglas reference")

        parsed = urlparse(value)
        bucket = parsed.netloc
        obj = parsed.path.lstrip("/")
        destination = parse_qs(parsed.query).get("destination", [""])[0]
        generation = int(parsed.fragment or 0)
        tmpfile = None

        if destination in ["tmpfile", "tempfile"]:
            tmpfile = tempfile.NamedTemporaryFile(prefix="berglas-",
                                                  delete=False)
        elif destination:
            tmpfile = open(destination, "wb")

        plaintext = self._access(bucket, obj, generation=generation)

        if tmpfile:
            os.chmod(tmpfile.name, stat.S_IRUSR | stat.S_IWUSR)
            tmpfile.write(plaintext)
            plaintext = tmpfile.name.encode()
            tmpfile.close()

        return plaintext
Пример #4
0
    def get_env(self) -> Dict[str, str]:
        name = (
            f"projects/{os.environ['X_GOOGLE_GCP_PROJECT']}/locations/{os.environ['X_GOOGLE_FUNCTION_REGION']}/"
            f"functions/{os.environ['X_GOOGLE_FUNCTION_NAME']}"
        )

        cloudfunctions_v1 = self._build("cloudfunctions", "v1")
        try:
            function = cloudfunctions_v1.projects().locations().functions().get(name=name).execute()
        except HttpError as e:
            if e.resp.status == http.client.NOT_FOUND.value:
                raise AutoException("function not found")
            elif e.resp.status == http.client.UNAUTHORIZED.value:
                raise AutoException("permission denied to fetch function info")

            raise AutoException(f"unable to fetch function: {e}")

        return function["environmentVariables"]
Пример #5
0
    def _get_from_metadata(path: str) -> str:
        path = f"http://metadata.google.internal/computeMetadata/v1/{path}"

        http_client = discovery.build_http()
        set_user_agent(http_client, USER_AGENT)
        response, content = http_client.request(path, headers={"Metadata-Flavor": "Google", "User-Agent": USER_AGENT})
        if response.status != http.client.OK.value:
            raise AutoException(f"failed to get metadata for: {path}: {content}")

        return content.decode()
Пример #6
0
def detect_runtime_environment() -> RuntimeEnv:
    """
    Finds the most likely runtime environment
    """
    if os.environ.get("X_GOOGLE_FUNCTION_NAME"):
        return CloudFunctionEnv()

    if os.environ.get("K_REVISION"):
        return CloudRunEnv()

    if os.environ.get("GAE_SERVICE"):
        return AppEngineEnv()

    raise AutoException("unknown runtime")
Пример #7
0
    def get_env(self) -> Dict[str, str]:
        version = os.environ["GAE_VERSION"]
        service = os.environ["GAE_SERVICE"]

        project = self._get_from_metadata("project/project-id")

        gae = self._build("appengine", "v1")
        try:
            service_info = (
                gae.apps()
                .services()
                .versions()
                .get(appsId=project, servicesId=service, versionsId=version, view="FULL")
                .execute()
            )
        except HttpError as e:
            if e.resp.status == http.client.NOT_FOUND.value:
                raise AutoException("app engine service not found")
            elif e.resp.status == http.client.UNAUTHORIZED.value:
                raise AutoException("permission denied to fetch app engine info")

            raise AutoException(f"unable to fetch app engine service: {e.content}")

        return service_info["envVariables"]
Пример #8
0
    def _access(self,
                bucket_name: str,
                path: str,
                generation: Optional[int] = None) -> bytes:
        """
        Get plaintext value of the secret stored at path in bucket
        """
        blob = self.storage_client.bucket(bucket_name).get_blob(
            path, client=self.storage_client, generation=generation)
        if not blob:
            raise AutoException("secret object not found")

        if not blob.metadata or not blob.metadata.get(METADATA_KMS_KEY):
            raise AutoException("missing kms key in secret metadata")

        key = blob.metadata[METADATA_KMS_KEY]
        data = blob.download_as_string()
        parts = data.split(b":", maxsplit=1)
        if len(parts) < 2:
            raise AutoException("invalid ciphertext: not enough parts")

        try:
            enc_dek = base64.decodebytes(parts[0])
        except binascii.Error:
            raise AutoException("invalid ciphertext: failed to parse dek")

        try:
            ciphertext = base64.decodebytes(parts[1])
        except binascii.Error:
            raise AutoException(
                "invalid ciphertext: failed to parse ciphertext")

        try:
            response = self.kms_client.decrypt(key, enc_dek,
                                               path.encode("UTF8"))
            dek = response.plaintext
        except (GoogleAPICallError, RetryError, ValueError):
            raise AutoException("failed to decrypt dek")

        return _decrypt_gcm(dek, ciphertext)