Snowpark Container Services:服务的其他注意事项

从容器内部连接到 Snowflake

当您启动服务(包括作业服务)时,Snowflake 会为正在运行的容器提供凭据,使容器代码能够使用驱动程序连接到 Snowflake 并执行 SQL(类似于连接到 Snowflake 的计算机上的任何其他代码)。提供的凭据以所有者角色(创建服务的角色)的身份进行身份验证。Snowflake 将提供一些信息作为容器中的环境变量。

Snowflake 中的每个对象都有一个所有者角色。服务的所有者角色决定了在与 Snowflake 交互时,服务可以执行哪些功能。这包括执行 SQL、访问暂存区和服务到服务网络。

备注

服务的所有者角色是指创建服务的角色。您还可以定义一个或多个服务角色来管理对服务公开的端点的访问。有关更多信息,请参阅 管理对服务端点的访问

当您创建服务时,Snowflake 还会创建特定于该服务的 服务用户。当服务运行查询时,它将使用服务的所有者角色作为服务用户运行查询。服务用户可以执行的操作由此所有者角色确定。

服务的所有者角色不能是任何特权角色,例如 ACCOUNTADMIN 和 SECURITYADMIN。这是为了限制行为不当的服务可以执行的操作,需要客户更加有意识地让服务能够执行管理操作。

要查看特定服务用户发出的查询,您可以使用 ACCOUNTADMIN 角色来查看 查询历史记录。服务用户的用户名以下列形式显示:

  • 对于 8.35 服务器版本之前创建的服务,服务用户名的格式为 SF$SERVICE$unique-id

  • 对于 8.35 服务器版本之后创建的服务,服务用户名与服务名称相同。

连接到 Snowflake

Snowflake 提供以下环境变量,供您在服务代码中配置 Snowflake 客户端:

  • SNOWFLAKE_ACCOUNT: 提供服务当前运行使用的 Snowflake 账户的 账户定位器

  • SNOWFLAKE_HOST: 提供用于连接到 Snowflake 的主机名。

Snowflake 还在名为 /snowflake/session/token 的文件内的容器中提供 OAuth 令牌。创建连接时,向连接器提供此令牌。

从容器创建到 Snowflake 的连接时,必须使用 SNOWFLAKE_HOST、SNOWFLAKE_ACCOUNT 和 OAuth 令牌。您不能使用 OAuth 令牌而不使用 SNOWFLAKE_HOST,也不能在 Snowpark Container Services 外使用 OAuth 令牌。有关更多信息,请参阅 使用 OAuth 令牌执行 SQL

备注

使用外部访问集成访问 Snowflake 可能意味着通过互联网发送潜在的敏感信息。服务应尽可能使用提供的 OAuth 令牌访问 SNOWFLAKE_HOST 主机名。这样就不必通过互联网访问 Snowflake。

有关使用各种 Snowflake 驱动程序的示例代码,请参阅 ` Snowflake 连接示例 <https://github.com/Snowflake-Labs/sf-samples/tree/main/samples/spcs/sf-connection (https://github.com/Snowflake-Labs/sf-samples/tree/main/samples/spcs/sf-connection)>`_。

示例

  • 在 :doc:` 教程 2 <tutorials/tutorial-2>` (请参阅 main.py)中,代码按如下方式读取环境变量:

    SNOWFLAKE_ACCOUNT = os.getenv('SNOWFLAKE_ACCOUNT')
    SNOWFLAKE_HOST = os.getenv('SNOWFLAKE_HOST')
    
    Copy

    代码将这些变量传递给所选 Snowflake 客户端的连接创建代码。容器使用这些凭据创建一个新会话,并将所有者角色作为主要角色,来执行查询。以下示例代码是使用 Python 创建 Snowflake 连接所需的最简代码:

    def get_login_token():
      with open('/snowflake/session/token', 'r') as f:
        return f.read()
    
    conn = snowflake.connector.connect(
      host = os.getenv('SNOWFLAKE_HOST'),
      account = os.getenv('SNOWFLAKE_ACCOUNT'),
      token = get_login_token(),
      authenticator = 'oauth'
    )
    
    Copy
  • 当使用默认主机(即,在创建连接时不纳入 host 实参)时,将支持使用其他形式的身份验证(如用户名和密码)连接到 Snowflake。例如,以下连接指定要进行身份验证的用户名和密码:

    conn = snowflake.connector.connect(
      account = os.getenv('SNOWFLAKE_ACCOUNT'),
      user = '<user-name>',
      password = <password>
    )
    
    Copy

    使用默认主机需要与网络规则进行外部访问集成,该规则允许从服务访问您账户的 Snowflake 主机名。例如,如果您的账户名称是 MyAccount,则主机名将为 myaccount.snowflakecomputing.cn。有关更多信息,请参阅 配置网络出口

    • 创建与您账户的 Snowflake API 主机名匹配的网络规则:

      CREATE OR REPLACE NETWORK RULE snowflake_egress_access
        MODE = EGRESS
        TYPE = HOST_PORT
        VALUE_LIST = ('myaccount.snowflakecomputing.cn');
      
      Copy
    • 使用上述网络规则创建集成:

      CREATE EXTERNAL ACCESS INTEGRATION snowflake_egress_access_integration
        ALLOWED_NETWORK_RULES = (snowflake_egress_access)
        ENABLED = true;
      
      Copy

配置用于执行 SQL 的数据库和架构上下文

本节介绍两个概念:

  • Snowflake 用于确定要在其中创建服务的数据库和架构的逻辑。

  • Snowflake 将此信息传达给容器,从而使容器代码能够在同一数据库和架构上下文中执行 SQL 的方法。

Snowflake 使用服务名称来确定要在其中创建服务的数据库和架构:

  • 示例 1:在以下 CREATE SERVICE 和 EXECUTE JOB SERVICE 命令中,服务名称不会显式指定数据库和架构名称。Snowflake 在当前数据库和架构中创建服务和作业服务。

    -- Create a service.
    CREATE SERVICE test_service IN COMPUTE POOL ...
    
    -- Execute a job service.
    EXECUTE JOB SERVICE
      IN COMPUTE POOL tutorial_compute_pool
      NAME = example_job_service ...
    
    Copy
  • 示例 2:在以下 CREATE SERVICE 和 EXECUTE JOB SERVICE 命令中,服务名称包括数据库和架构名称。Snowflake 在指定的数据库 (test_db) 和架构 (test_schema) 中创建服务和作业服务,无论当前架构如何。

    -- Create a service.
    CREATE SERVICE test_db.test_schema.test_service IN COMPUTE POOL ...
    
    -- Execute a job service.
    EXECUTE JOB SERVICE
      IN COMPUTE POOL tutorial_compute_pool
      NAME = test_db.test_schema.example_job_service ...
    
    Copy

当 Snowflake 启动服务时,它会使用以下环境变量向正在运行的容器提供数据库和架构信息:

  • SNOWFLAKE_DATABASE

  • SNOWFLAKE_SCHEMA

容器代码可以使用连接代码中的环境变量来确定要使用的数据库和架构,如以下示例所示:

conn = snowflake.connector.connect(
  host = os.getenv('SNOWFLAKE_HOST'),
  account = os.getenv('SNOWFLAKE_ACCOUNT'),
  token = get_login_token(),
  authenticator = 'oauth',
  database = os.getenv('SNOWFLAKE_DATABASE'),
  schema = os.getenv('SNOWFLAKE_SCHEMA')
)
Copy

示例

教程 2 中,您可以创建与 Snowflake 连接并执行 SQL 语句的 Snowflake 作业服务。以下步骤总结了教程代码如何使用环境变量:

  1. 在通用设置中(请参阅 通用设置 部分),您可以创建资源,包括数据库和架构。您还可以为会话设置当前数据库和架构:

    USE DATABASE tutorial_db;
    ...
    USE SCHEMA data_schema;
    
    Copy
  2. 创建作业服务(通过运行 EXECUTE JOB SERVICE)后,Snowflake 启动容器,并将容器中的以下环境变量设置为会话的当前数据库和架构:

    • 将 SNOWFLAKE_DATABASE 设置为“TUTORIAL_DB”

    • 将 SNOWFLAKE_SCHEMA 设置为“DATA_SCHEMA”

  3. 作业代码(请参阅教程 2 中的 main.py)读取以下环境变量:

    SNOWFLAKE_DATABASE = os.getenv('SNOWFLAKE_DATABASE')
    SNOWFLAKE_SCHEMA = os.getenv('SNOWFLAKE_SCHEMA')
    
    Copy
  4. 作业代码将数据库和架构设置为执行 SQL 语句(main.py 中的 run_job() 函数)的上下文:

    {
       "account": SNOWFLAKE_ACCOUNT,
       "host": SNOWFLAKE_HOST,
       "authenticator": "oauth",
       "token": get_login_token(),
       "warehouse": SNOWFLAKE_WAREHOUSE,
       "database": SNOWFLAKE_DATABASE,
       "schema": SNOWFLAKE_SCHEMA
    }
    ...
    
    Copy

    备注

    SNOWFLAKE_ACCOUNT、SNOWFLAKE_HOST、SNOWFLAKE_DATABASE、SNOWFLAKE_SCHEMA 是 Snowflake 为应用程序容器生成的环境变量,但 SNOWFLAKE_WAREHOUSE 不是(教程 2 应用程序代码创建了此变量,因为 Snowflake 不会将仓库名称传递给容器)。

为容器指定仓库

如果服务连接到 Snowflake,以在 Snowflake 仓库中执行查询,则您可以使用以下选项来指定仓库:

  • 在应用程序代码中指定仓库。 在启动 Snowflake 会话以在代码中运行查询时,在连接配置中指定仓库。有关示例,请参阅 教程 2

  • 在创建服务时指定默认仓库。CREATE SERVICE (或 EXECUTE JOB SERVICE )命令中指定可选的 QUERY_WAREHOUSE 参数,以提供默认仓库。如果应用程序代码未在连接配置中提供仓库,则 Snowflake 将使用默认仓库。使用 ALTER SERVICE 命令更改默认仓库。

如果同时使用这两种方法指定仓库,则将使用应用程序代码中指定的仓库。

使用 OAuth 令牌执行 SQL

所有 Snowflake 提供的客户端都支持使用 OAuth 作为身份验证方式。服务容器还使用 OAuth 机制来向 Snowflake 进行身份验证。例如,当容器要执行时 SQL 时,容器将创建与 Snowflake 的连接,类似于任何其他 Snowflake 客户端:

def get_login_token():
  with open('/snowflake/session/token', 'r') as f:
    return f.read()

conn = snowflake.connector.connect(
  host = os.getenv('SNOWFLAKE_HOST'),
  account = os.getenv('SNOWFLAKE_ACCOUNT'),
  token = get_login_token(),
  authenticator = 'oauth'
)
Copy

当您创建服务时,Snowflake 会运行容器,并为容器提供 Oauth 令牌,以便在容器中的以下位置使用:/snowflake/session/token

请注意以下有关此 OAuth 令牌的详细信息:

  • 您应该先阅读 /snowflake/session/token 文件的内容然后再立即使用,因为内容将在 10 分钟后过期,而 Snowflake 每隔几分钟就会刷新一次此文件。容器成功连接到 Snowflake 后,过期时间将不适用于该连接(与用户直接创建的任何会话一样)。

  • 此 OAuth 令牌仅在特定的 Snowflake 服务中有效。您无法复制 OAuth 令牌并在服务外使用它。

  • 使用 OAuth 令牌,容器作为服务用户与 Snowflake 连接,并使用服务的所有者角色。

  • 使用 OAuth 令牌进行连接将创建一个新会话。OAuth 令牌不与任何现有 SQL 会话关联。

    备注

    执行存储过程和执行服务间的一个显著差异是,存储过程与运行存储过程的 SQL 在同一会话中运行。但是,每次容器建立新连接时,您都会创建一个新会话。

使用调用方限从容器内部连接到 Snowflake

容器通过以服务用户身份连接到 Snowflake 并使用授予服务特权的服务所有者角色来执行查询。在某些应用场景中,您可能需要使用最终用户而不是服务用户的上下文来执行查询。在此上下文中会用到调用方的权限功能。

例如,假设您要创建一个公开公共端点的服务,以提供使用存储在 Snowflake 中的数据来显示仪表板的 Web 应用程序。您授予 Snowflake 账户中的其他用户对仪表板的访问权限(通过授予这些用户 服务角色)。当这些最终用户中的任何一个登录到仪表板时,您希望仪表板仅显示用户有权访问的数据。

但是,无论哪个最终用户连接到端点,由于容器默认情况下使用服务用户和服务所有者角色执行查询,仪表板都只会显示服务所有者角色有权访问的数据。因此,用户仪表板中的数据不限于最终用户有权访问的内容,这导致用户可以访问不应有权访问的数据。

要限制仪表板仅显示登录用户可访问的数据,应用程序容器需要使用授予最终用户的权限执行 SQL。您可以通过在应用程序中使用调用方权限来启用此功能。

备注

  • 仅在使用网络入口 访问服务 时支持调用方权限功能。使用服务函数访问服务时,该功能不可用。

  • |native-app|(使用容器的应用程序)当前不支持调用方权限功能。

为您的服务配置调用方权限

为您的应用程序配置调用方权限的流程包括两个步骤。

  1. 服务规范 中将 executeAsCaller 设置为 true,如以下规范片段所示:

    spec:
      containers:
      ...
    capabilities:
      securityContext:
        executeAsCaller: true
    
    Copy

    这样做会明确告诉 Snowflake 应用程序打算使用调用方权限,并使 Snowflake 在将请求发送到应用程序容器之前在每个传入请求中插入 Sf-Context-Current-User-Token 标头。此用户令牌有助于以调用用户的身份执行查询。如果未指定,则 executeAsCaller 默认为 false

    指定 executeAsCaller 选项不会影响服务以服务用户身份和服务所有者角色执行查询的能力。启用 executeAsCaller 后,服务可以选择以调用用户和服务用户身份连接到 Snowflake。

  2. 更新应用程序代码。要代表调用用户建立 Snowflake 连接,请更新代码以创建登录令牌,该令牌包括 Snowflake 提供给服务 <label-snowpark_containers_connect_to_snowflake>` 的 OAuth 令牌和来自 ``Sf-Context-Current-User-Token` 标头的用户令牌。登录令牌应采用以下格式:<service-oauth-token>.<Sf-Context-Current-User-Token>

    以下 Python 代码片段演示了这一点:

    # Environment variables below will be automatically populated by Snowflake.
    SNOWFLAKE_ACCOUNT = os.getenv("SNOWFLAKE_ACCOUNT")
    SNOWFLAKE_HOST = os.getenv("SNOWFLAKE_HOST")
    
    def get_login_token():
        with open("/snowflake/session/token", "r") as f:
            return f.read()
    
    def get_connection_params(ingress_user_token = None):
        # start a Snowflake session as ingress user
        # (if user token header provided)
        if ingress_user_token:
            logger.info("Creating a session on behalf of the current user.")
            token = get_login_token() + "." + ingress_user_token
        else:
            logger.info("Creating a session as the service user.")
            token = get_login_token()
    
        return {
            "account": SNOWFLAKE_ACCOUNT,
            "host": SNOWFLAKE_HOST,
            "authenticator": "oauth",
            "token": token
        }
    
    def run_query(request, query):
        ingress_user_token = request.headers.get('Sf-Context-Current-User-Token')
        # ingress_user_token is None if header not present
        connection_params = get_connection_params(ingress_user_token)
        with Session.builder.configs(connection_params).create() as session:
          # use the session to execute a query.
    
    Copy

在上面的例子中:

  • get_login_token 函数会读取 Snowflake 在其中复制 OAuth 令牌供容器使用的文件。

  • get_connection_params 函数通过将 OAuth 令牌和来自 Sf-Context-Current-User-Token 标头的用户令牌级联来构成令牌。该函数将此令牌包含在应用程序用来连接到 Snowflake 的参数字典中。

有关包含分步说明的示例,请参阅 使用已启用的调用方权限创建服务

访问已配置调用方权限的服务

配置调用方权限是指服务代表调用方建立 Snowflake 连接。登录服务入口端点的方式(以编程方式或使用浏览器)保持不变。登录后,以下内容适用:

  • 使用浏览器访问公共端点:登录到端点后,服务将代表调用用户,使用该用户的默认角色建立与 Snowflake 的连接。如果没有为用户配置默认角色,则使用 PUBLIC 角色。

  • 以编程方式访问公共端点:使用 JWT 令牌 以编程方式登录到端点 时,可以选择设置 scope 参数以指定要激活的角色

目前,在服务代表调用方与 Snowflake 建立调用方权限连接后,不支持切换角色。如果您的应用程序需要使用不同的角色来访问不同的对象,请将用户设置为默认激活所有辅助角色。为此,请使用 ALTER USER 命令将用户的 DEFAULT_SECONDARY_ROLES 属性设置为 ('ALL'):

ALTER USER my_user SET DEFAULT_SECONDARY_ROLES = ( 'ALL' );
Copy

管理服务的调用方授权

当服务创建调用方权限会话时,该会话会以调用用户(而不是服务用户)的身份运行。当使用此会话执行操作时,Snowflake 应用两项权限检查:

  1. 第一项权限检查与用户直接创建会话相同。这些是 Snowflake 为用户执行的正常权限检查。

  2. 第二项权限检查则验证是否允许服务代表用户执行该操作。Snowflake 通过确保服务所有者角色已被授予必要的调用方权限来验证这一点。

在调用方权限会话中,正常权限检查和服务所有者角色的 调用方授权 检查都必须允许操作;这称为 受限调用方权限。默认情况下,服务无权代表用户执行任何操作。您必须明确为服务授予调用方权限,以便它能够以调用方权限运行。

例如,假设用户 U1 使用角色 R1,该角色在表 T1 上具有 SELECT 权限。当 U1 登录到服务的公共端点 (example_service) 时(该端点配置为使用调用方权限),服务将代表 U1 与 Snowflake 建立连接。

要允许服务代表 U1 查询表 T1,您需要为服务的所有者角色授予以下权限:

  1. 解析表名称的权限,方法是授予调用方权限,允许服务以该表的数据库和架构的 USAGE 权限运行。

  2. 使用仓库执行查询的权限,方法是授予调用方权限,允许服务以仓库的 USAGE 权限运行。

  3. 通过授予调用者权限来查询表的权限,该权限允许服务以表 T1 上的 SELECT 权限运行。

-- Permissions to resolve the table's name.
GRANT CALLER USAGE ON DATABASE <db_name> TO ROLE <service_owner_role>;
GRANT CALLER USAGE ON SCHEMA <schema_name> TO ROLE <service_owner_role>;
-- Permissions to use a warehouse
GRANT CALLER USAGE ON WAREHOUSE <warehouse_name> TO ROLE <service_owner_role>;
-- Permissions to query the table.
GRANT CALLER SELECT ON TABLE T1 TO ROLE <service_owner_role>;
Copy

您账户中任何具有全局 MANAGE CALLER GRANT 权限的角色都可以授予调用方权限。有关调用方授权的详细信息,请参阅 GRANT CALLER受限的调用方权限

示例

提供了在代表用户执行 SQL 查询时使用调用方权限功能的服务示例。有关更多信息,请参阅 使用已启用的调用方权限创建服务

配置网络入口

要允许任何内容通过互联网与服务交互,请在服务规范文件中将服务侦听的网络端口声明为端点。这些端点控制入口。

默认情况下,服务端点是专用的。只有 服务函数服务到服务通信 可以向专用端点发出请求。您可以将端点声明为公共端点,以允许通过互联网向端点发出请求。服务的所有者角色必须具有账户的 BIND SERVICE ENDPOINT 权限。

endpoints:
- name: <endpoint name>
  port: <port number>
  protocol : < TCP / HTTP / HTTPS >
  public: true
Copy

有关示例,请参阅 教程 1

备注

目前只有服务支持网络入口,作业服务不支持。

入口和 Web 应用程序安全性

您可以使用公共端点支持(网络入口)创建用于 Web 托管的 Snowpark Container Services。为了提高安全性,Snowflake 采用代理服务来监控从客户端到服务的传入请求以及从服务到客户端的传出响应。本节介绍代理的作用以及它如何影响部署到 Snowpark Container Services 的服务。

备注

在本地测试服务时,您没有使用 Snowflake 代理,因此在本地运行服务的体验与在 Snowpark Container Services 中部署的体验之间存在差异。查看本节并更新本地设置以更好地进行测试。

例如:

  • 如果请求使用了被禁止的 HTTP 方法,代理不会转发传入的 HTTP 请求。

  • 如果响应中的 Content-Type 标头指出响应包含可执行文件,则代理会向客户端发送 403 响应。

此外,代理还可以在请求和响应中注入新的标头并更改现有标头,同时兼顾容器和数据安全性。

例如,在收到请求后,服务可能会在响应中,将 HTML、JavaScript、CSS 以及其他网页内容发送到客户端浏览器。浏览器中的网页是服务的一部分,充当用户界面。出于安全原因,如果服务有限制(例如,限制与其他站点建立网络连接),您可能还希望服务网页具有同样的限制。

默认情况下,服务访问互联网的权限存在限制。在大多数情况下,浏览器还应限制客户端应用程序访问互联网和潜在的数据共享行为。如果设置外部访问集成 (EAI) 以允许服务访问 example.com (请参阅 配置网络出口 ),服务网页也应该能够通过浏览器访问 example.com

Snowflake 代理通过在响应中添加 Content-Security-Policy (CSP) 标头,对服务和网页应用同样的网络限制。默认情况下,代理会在响应中添加基线 CSP,以防范常见的安全威胁。浏览器安全性旨在尽最大努力在功能和安全性之间取得平衡,确保这个基准适用于您的用例是大家共同的责任。此外,如果服务配置为使用 EAI,代理将对网页从 EAI 到 CSP 应用同样的网络规则。此 CSP 使浏览器中的网页能够访问与服务相同的站点。

以下各节介绍了 Snowflake 代理如何处理对服务的传入请求,并修改服务对客户端的传出响应。

传入服务的请求

当请求到达时,代理会先执行以下操作,然后再将请求转发到服务:

  • 采用禁止的 HTTP 方法的传入请求: 如果传入 HTTP 请求使用以下任意被禁止的 HTTP 方法,代理不会将请求转发到服务:

    • TRACE

    • OPTIONS

    • CONNECT

传出到客户端的响应

Snowflake 代理会先将这些修改应用于服务发送的响应,然后再将响应转发到客户端。

  • 标头清理: Snowflake 代理会删除这些响应标头(如果存在):

    • X-XSS-Protection

    • Server

    • X-Powered-By

    • Public-Key-Pins

  • Content-Type 响应标头: 如果服务响应包含 Content-Type 标头以及以下任何 MIME 类型值(指示可执行文件),Snowflake 代理不会将该响应转发到客户端。相反,代理会发送一个 403 Forbidden 响应。

    • application/x-msdownload:Microsoft 可执行文件。

    • application/exe:通用可执行文件。

    • application/x-exe:另一个通用可执行文件。

    • application/dos-exe:DOS 可执行文件。

    • application/x-winexe:Windows 可执行文件。

    • application/msdos-windows:MS-DOS Windows 可执行文件。

    • application/x-msdos-program:MS-DOS 可执行文件。

    • application/x-sh:Unix shell 脚本。

    • application/x-bsh:Bourne shell 脚本。

    • application/x-csh:C shell 脚本。

    • application/x-tcsh:Tcsh shell 脚本。

    • application/batch:Windows 批处理文件。

  • X-Frame-Options 响应标头: 为防止点击劫持攻击,Snowflake 代理将此响应标头设置为 DENY,以防止其他网页使用 iframe 访问服务网页。

  • Cross-Origin-Opener-Policy (COOP) 响应标头: Snowflake 将 COOP 响应标头设置为 same-origin 以防止引用跨域窗口访问服务选项卡。

  • Cross-Origin-Resource-Policy (CORP) 响应标头: Snowflake 将 CORP 标头设置为 same-origin 以防止外部站点加载入口端点公开的资源(例如,在 iframe 中)。

  • X-Content-Type-Options 响应标头: Snowflake 代理将此标头设置为 nosniff,以确保客户端不会更改服务在响应中指出的 MIME 类型。

  • Cross-Origin-Embedder-Policy (COEP) 响应标头: Snowflake 代理将 COEP 响应标头设置为 credentialless,这意味着在加载跨域对象(如图像或脚本)时,如果远程对象不支持跨域资源共享 (CORS) 协议,Snowflake 在加载时不会发送凭据。

  • Content-Security-Policy-Report-Only 响应标头: Snowflake 代理将此响应标头替换为新值,指示客户端向 Snowflake 发送 CSP 报告。

  • Content-Security-Policy (CSP) 响应标头: 默认情况下,Snowflake 代理会添加以下基线 CSP,以防止常见的 Web 攻击。

    default-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data:; object-src 'none'; connect-src 'self'; frame-ancestors 'self';
    
    Copy

    有两个内容安全策略注意事项:

    • 除了代理添加的基线内容安全策略外,服务本身还可以在响应中显式添加 CSP。服务可能会选择通过添加更严格的 CSP 来增强安全性。例如,服务可能会添加以下 CSP 以仅允许来自 self 的脚本。

      script-src 'self'
      
      Copy

      在发送给客户端的结果响应中,将有两个 CSP 标头。收到响应后,客户端浏览器将应用最严格的内容安全策略,其中包括每个策略指定的其他限制。

    • 如果配置外部访问集成 (EAI) 以允许服务访问外部站点 (配置网络出口),Snowflake 代理会创建一个 CSP,以允许网页访问该站点。例如,假设与 EAI 关联的网络规则允许服务出口访问 example.com。然后,Snowflake 代理将添加此 CSP 响应标头:

      default-src 'self' 'unsafe-inline' 'unsafe-eval' http://example.com https://example.com blob: data:; object-src 'none'; connect-src 'self' http://example.com https://example.com wss://example.com; frame-ancestors 'self';
      
      Copy

      浏览器遵循响应中收到的内容访问策略。在此示例中,浏览器允许应用程序访问 example.com, 但不允许访问其他站点。

入口和 SSO 注意事项

当从互联网访问公共端点时,您可能会发现用户名/密码身份验证正常,但 SSO 导致空白页或错误:“OAuth client integration with the given client ID is not found.”

如果将旧版本的联合身份验证 (SSO) 用于 Snowflake,而不是使用 配置 Snowflake 以使用联合身份验证 中说明的较新安全集成版本,就会出现这种情况。执行以下操作以验证:

  1. 运行以下查询:

    SHOW PARAMETERS LIKE 'SAML_IDENTITY_PROVIDER' IN ACCOUNT;
    
    Copy

    如果您设置了此参数,那么在某些时候您使用的是旧版本的联合身份验证。

  2. 如果设置了前面的参数,请运行以下查询以查看是否进行了 SAML 安全集成:

    SHOW INTEGRATIONS;
    SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) WHERE "type" = 'SAML2';
    
    Copy

    如果您没有任何 SAML2 类型的集成,那么您使用的是旧版本的联合身份验证。

在这种情况下,解决方案是从旧版本联合身份验证迁移到新的集成式联合身份验证。有关更多信息,请参阅 迁移到 SAML2 安全集成

配置网络出口

应用程序代码可能需要访问互联网。默认情况下,应用程序容器没有访问互联网的权限。您需要使用 外部访问集成 (EAIs) 启用互联网访问。

通常,您希望账户管理员创建 EAIs 来管理允许通过服务(包括作业服务)进行的外部访问。然后,账户管理员可以向开发者用于运行服务的特定角色授予 EAI 使用量。

以下示例概述了创建 EAI 的步骤,这允许出口流量流向使用网络规则制定的特定目的地。然后,您可以在创建服务时引用 EAI 以允许对特定互联网目的地的请求。

示例

假设您希望应用程序代码将请求发送到以下目的地:

  • 向 translation.googleapis.com 发出 HTTPS 请求

  • 向 google.com 发出 HTTP 和 HTTPS 请求

按照以下步骤操作,使服务能够访问互联网上的这些域:

  1. 创建外部访问集成 (EAI)。这需要适当的权限。例如,您可以使用 ACCOUNTADMIN 角色来创建 EAI。这个过程分为两步:

    1. 使用 CREATE NETWORK RULE 命令创建一个或多个出口网络规则,列出您希望允许访问的外部目的地。您可以使用一个网络规则来完成此示例,但为便于说明,我们创建了两个网络规则:

      1. 创建名为 translate_network_rule 的网络规则:

        CREATE OR REPLACE NETWORK RULE translate_network_rule
          MODE = EGRESS
          TYPE = HOST_PORT
          VALUE_LIST = ('translation.googleapis.com');
        
        Copy

        此规则允许 TCP 连接到 translation.googleapis.com 目的地。VALUE_LIST 属性中的域未指定可选端口号,因此将使用默认端口 443 (HTTPS)。这使应用程序可以连接到以 https://translation.googleapis.com/ 开头的任何 URL。

      2. 创建名为 google_network_rule 的网络规则:

        CREATE OR REPLACE NETWORK RULE google_network_rule
          MODE = EGRESS
          TYPE = HOST_PORT
          VALUE_LIST = ('google.com:80', 'google.com:443');
        
        Copy

        这使应用程序可以连接到任何以 http://google.com/https://google.com/ 开头的 URL。

      备注

      对于 VALUE_LIST 参数,您必须提供完整的主机名。不支持通配符(例如,*.googleapis.com)。

      Snowpark Container Services 仅支持允许端口 22、80、443 和 1024+ 的网络规则。如果引用的网络规则允许访问其他端口,则服务创建将失败。如果您需要使用其他端口,请联系客户代表。

      备注

      要允许服务向互联网上的任何目的地发送 HTTP 或 HTTPS 请求,请指定“0.0.0.0”作为 VALUE_LIST 属性。以下网络规则允许在互联网上的任何位置发送“HTTP”和“HTTPS”请求。“0.0.0.0”仅支持端口 80 或 443。

      CREATE NETWORK RULE allow_all_rule
        TYPE = 'HOST_PORT'
        MODE= 'EGRESS'
        VALUE_LIST = ('0.0.0.0:443','0.0.0.0:80');
      
      Copy
    2. 创建外部访问集成 (EAI),指定允许使用上述两个出口网络规则:

      CREATE EXTERNAL ACCESS INTEGRATION google_apis_access_integration
        ALLOWED_NETWORK_RULES = (translate_network_rule, google_network_rule)
        ENABLED = true;
      
      Copy

      现在,账户管理员可以向开发者授予集成使用量,以允许他们运行可以访问互联网上特定目的地的服务。

      GRANT USAGE ON INTEGRATION google_apis_access_integration TO ROLE test_role;
      
      Copy
  2. 按照以下示例,通过提供 EAI 创建服务。创建服务的所有者角色需要 EAI 的 USAGE 权限和引用密钥的 READ 权限。请注意,您不能使用 ACCOUNTADMIN 角色来创建服务。

    • 创建服务:

      USE ROLE test_role;
      
      CREATE SERVICE eai_service
        IN COMPUTE POOL MYPOOL
        EXTERNAL_ACCESS_INTEGRATIONS = (GOOGLE_APIS_ACCESS_INTEGRATION)
        FROM SPECIFICATION
        $$
        spec:
          containers:
            - name: main
              image: /db/data_schema/tutorial_repository/my_echo_service_image:tutorial
              env:
                TEST_FILE_STAGE: source_stage/test_file
              args:
                - read_secret.py
          endpoints:
            - name: read
              port: 8080
        $$;
      
      Copy

      此示例 CREATE SERVICE 请求使用内联服务规范,并指定可选的 EXTERNAL_ACCESS_INTEGRATIONS 属性以纳入 EAI。EAI 指定允许从服务到特定目的地的出口流量的网络规则。

    • 执行作业服务:

      EXECUTE JOB SERVICE
        IN COMPUTE POOL tt_cp
        NAME = example_job_service
        EXTERNAL_ACCESS_INTEGRATIONS = (GOOGLE_APIS_ACCESS_INTEGRATION)
        FROM SPECIFICATION $$
        spec:
          container:
          - name: curl
            image: /tutorial_db/data_schema/tutorial_repo/alpine-curl:latest
            command:
            - "curl"
            - "http://google.com/"
        $$;
      
      Copy

      此示例 EXECUTE JOB SERVICE 命令指定内联规范和可选的 EXTERNAL_ACCESS_INTEGRATIONS 属性以纳入 EAI。这允许从作业到 EAI 允许的网络规则中指定的目的地的出口流量。

使用专用连接的网络出口

您可能会选择通过 专用连接端点 来引导服务的出口流量,而不是通过公共互联网路由网络出口。

首先需要在 Snowflake 账户中创建专用连接端点。然后配置网络规则以允许传出流量使用 专用连接。设置外部访问集成 (EAI) 的过程与上一节中所述的过程相同。

备注

专用通信要求 Snowflake 和客户的云账户使用相同的云提供商和相同的区域。

例如,如果您想通过专用连接启用服务对 Amazon S3 桶的出站互联网访问,请执行以下操作:

  1. 启用自维护端点服务 (Amazon S3) 的专用链接连接。有关分步说明,请参阅 Amazon S3 的 AWS 专用链接 (https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html)。

  2. 调用 SYSTEM$PROVISION_PRIVATELINK_ENDPOINT 系统函数,以在 Snowflake VNet 中预置专用连接端点。这使 Snowflake 能够使用专用连接连接到外部服务(在本示例中为 Amazon S3)。

    USE ROLE ACCOUNTADMIN;
    
    SELECT SYSTEM$PROVISION_PRIVATELINK_ENDPOINT(
      'com.amazonaws.us-west-2.s3',
      '*.s3.us-west-2.amazonaws.com'
    );
    
    Copy
  3. 在云提供商账户中,批准该端点。在本示例中,对于 Amazon AWS,请参阅 AWS 文档中的 接受或拒绝连接请求 (https://docs.aws.amazon.com/vpc/latest/privatelink/configure-endpoint-service.html#accept-reject-connection-requests)。 另外,要批准 Azure 中的端点,请参阅 Azure 文档 (https://learn.microsoft.com/en-us/azure/private-link/manage-private-endpoint?tabs=manage-private-link-powershell#private-endpoint-connections)。

  4. 使用 CREATE NETWORK RULE 命令创建出口网络规则,指定您希望允许访问的外部目的地。

    CREATE OR REPLACE NETWORK RULE private_link_network_rule
      MODE = EGRESS
      TYPE = PRIVATE_HOST_PORT
      VALUE_LIST = ('<bucket-name>.s3.us-west-2.amazonaws.com');
    
    Copy

    TYPE 参数值已设置为 PRIVATE_HOST_PORT。这表示网络规则允许传出网络流量使用 专用连接

  5. 创建 EAI 并使用它创建服务的其余步骤与上一节中说明的步骤相同(请参阅 配置网络出口)。

有关使用专用连接端点的更多信息,请参阅以下内容:

配置容器间的网络通信

有两个注意事项:

  • 服务实例的容器之间的通信: 如果一个服务实例运行多个容器,这些容器可以通过 localhost 相互通信(无需在服务规范中定义端点)。

  • 跨多个服务或多个服务实例的容器之间的通信: 属于不同服务(或同一服务的不同实例)的容器可以使用规范文件中定义的端点进行通信。有关更多信息,请参阅 服务到服务通信

使用 Snowflake 密钥将凭据传递到容器

您可能需要将 Snowflake 托管凭据传递到容器中的原因有很多。例如,您的服务可能与外部端点(Snowflake 外)通信,在这种情况下,您需要在容器中提供凭据信息,以便应用程序代码使用。

要提供凭据,请先将凭据存储在 Snowflake 密钥 对象中。然后,在服务规范中,使用 containers.secrets 指定要使用的密钥对象,以及将它们放置在容器内的哪个位置。您可以将这些凭据传递给容器中的环境变量,也可以使其在容器的本地文件中可用。

指定 Snowflake 密钥

通过名称或引用指定 Snowflake 密钥(引用仅适用于原生应用程序场景):

  • 通过名称传递 Snowflake 密钥: 您可以将密钥名称作为 snowflakeSecret 字段值传递。

    ...
    secrets:
    - snowflakeSecret:
        objectName: '<secret-name>'
      <other info about where in the container to copy the secret>
      ...
    
    Copy

    请注意,您可以选择指定 <secret-name> directly as the `` snowflakeSecret`` 值。

  • 通过引用传递 Snowflake 密钥: 使用 Snowpark Container Services 创建原生应用程序(带有容器的应用程序)时,应用程序生产者和使用者使用不同的 Snowflake 账户。在某些情况下,已安装的 Snowflake 原生应用程序需要访问使用者账户中存在于 APPLICATION 对象外部的现有密钥对象。在这种情况下,开发者可以使用“secrets by reference”规范语法来处理凭据,如下所示:

    containers:
    - name: main
      image: <url>
      secrets:
      - snowflakeSecret:
          objectReference: '<reference-name>'
        <other info about where in the container to copy the secret>
    
    Copy

    请注意,该规范使用 objectReference``(而不是 ``objectName)来提供密钥引用名称。

指定容器内的密钥位置

您可以告诉 Snowflake 将密钥作为环境变量放在容器中,或者将它们写入本地容器文件中。

将密钥作为环境变量传递

要将 Snowflake 密钥作为环境变量传递给容器,请在 containers.secrets 字段中添加 envVarName

containers:
- name: main
  image: <url>
  secrets:
  - snowflakeSecret: <secret-name>
    secretKeyRef: username | password | secret_string |  'access_token'
    envVarName: '<env-variable-name>'
Copy

secretKeyRef 值取决于 Snowflake 密钥的类型。可能的值如下:

  • 如果 Snowflake 密钥是 password 类型,则为 usernamepassword

  • 如果 Snowflake 密钥是 generic_string 类型,则为 secret_string

请注意,Snowflake 不会在创建服务后更新作为环境变量传递的密钥。

示例 1:将 password 类型的密钥作为环境变量传递

在此示例中,您将创建以下 password 类型的 Snowflake 密钥对象:

CREATE SECRET testdb.testschema.my_secret_object
  TYPE = password
  USERNAME = 'snowman'
  PASSWORD = '1234abc';
Copy

要在容器中将此 Snowflake 密钥对象提供给环境变量(例如,LOGIN_USERLOGIN_PASSWORD),请在规范文件中添加以下 containers.secrets 字段:

containers:
- name: main
  image: <url>
  secrets:
  - snowflakeSecret: testdb.testschema.my_secret_object
    secretKeyRef: username
    envVarName: LOGIN_USER
  - snowflakeSecret: testdb.testschema.my_secret_object
    secretKeyRef: password
    envVarName: LOGIN_PASSWORD
Copy

在本示例中,snowflakeSecret 值是完全限定的对象名称,因为密钥可以存储在与正在创建的服务不同的架构中。

此示例中的 containers.secrets 字段是两个 snowflakeSecret 对象的列表:

  • 第一个对象将 Snowflake 密钥对象中的 username 映射到容器中的 LOGIN_USER 环境变量。

  • 第二个对象将 Snowflake 密钥对象中的 password 映射到容器中的 LOGIN_PASSWORD 环境变量。

示例 2:将 generic_string 类型的密钥作为环境变量传递

在此示例中,您将创建以下 generic_string 类型的 Snowflake 密钥对象:

CREATE SECRET testdb.testschema.my_secret
  TYPE=generic_string
  SECRET_STRING='
       some_magic: config
  ';
Copy

要在容器中将此 Snowflake 密钥对象提供给环境变量(例如,GENERIC_SECRET),请在规范文件中添加以下 containers.secrets 字段:

containers:
- name: main
  image: <url>
  secrets:
  - snowflakeSecret: testdb.testschema.my_secret
    secretKeyRef: secret_string
    envVarName: GENERIC_SECRET
Copy

在本地容器文件中写入密钥

要使 Snowflake 密钥可用于本地容器文件中的应用程序容器,请添加 containers.secrets 字段:要使 Snowflake 密钥可用于本地容器文件中的应用程序容器,请在 containers.secrets 中添加 directoryPath

containers:
- name: <name>
  image: <url>
  ...
  secrets:
  - snowflakeSecret: <snowflake-secret-name>
    directoryPath: '<local directory path in the container>'
Copy

Snowflake 在此指定的 directoryPath 中填充密钥所需的文件;不必指定 secretKeyRef。Snowflake 会根据密钥类型在容器中您提供的目录路径下创建以下文件:

  • 如果 Snowflake 密钥是 password 类型,则为 usernamepassword

  • 如果 Snowflake 密钥是 generic_string 类型,则为 secret_string

  • 如果 Snowflake 密钥是 oauth2 类型,则为 access_token

备注

创建完服务后,如果更新了 Snowflake 密钥对象,Snowflake 将更新正在运行的容器中对应的密钥文件。

示例 1:在本地容器文件中传递 password 类型的密钥

在此示例中,您将创建以下 password 类型的 Snowflake 密钥对象:

CREATE SECRET testdb.testschema.my_secret_object
  TYPE = password
  USERNAME = 'snowman'
  PASSWORD = '1234abc';
Copy

要使这些凭据在本地容器文件中可用,请在规范文件中添加以下 containers.secrets 字段:

containers:
- name: main
  image: <url>
  secrets:
  - snowflakeSecret: testdb.testschema.my_secret_object
    directoryPath: '/usr/local/creds'
Copy

当您启动服务时,Snowflake 会在容器中创建两个文件:/usr/local/creds/username/usr/local/creds/password。然后,应用程序代码可以读取这些文件。

示例 2:在本地容器文件中传递 generic_string 类型的密钥

在此示例中,您将创建以下 generic_string 类型的 Snowflake 密钥对象:

CREATE SECRET testdb.testschema.my_secret
  TYPE=generic_string
  SECRET_STRING='
       some_magic: config
  ';
Copy

要在本地容器文件中提供此 Snowflake 密钥对象,请在规范文件中添加以下 containers.secrets 字段:

containers:
- name: main
  image: <url>
  secrets:
  - snowflakeSecret: testdb.testschema.my_secret
    directoryPath: '/usr/local/creds'
Copy

当您启动服务时,Snowflake 会在容器中创建以下文件:/usr/local/creds/secret_string

示例 3:在本地容器文件中传递 oauth2 类型的密钥

在此示例中,您将创建以下 oauth2 类型的 Snowflake 密钥对象:

CREATE SECRET testdb.testschema.oauth_secret
  TYPE = OAUTH2
  OAUTH_REFRESH_TOKEN = '34n;vods4nQsdg09wee4qnfvadH'
  OAUTH_REFRESH_TOKEN_EXPIRY_TIME = '2023-12-31 20:00:00'
  API_AUTHENTICATION = my_integration;
Copy

要使这些凭据在本地容器文件中可用,请在规范文件中添加以下 containers.secrets 字段:

containers:
- name: main
  image: <url>
  secrets:
  - snowflakeSecret: testdb.testschema.oauth_secret
    directoryPath: '/usr/local/creds'
Copy

Snowflake 从 OAuth 密钥对象中提取访问令牌,并在容器中创建 /usr/local/creds/access_token

当服务使用 oauth2 类型的密钥时,该服务将使用该密钥访问互联网目的地。oauth 密钥必须得到 外部访问集成 (EAI) 的允许;否则,CREATE SERVICE 或 EXECUTE JOB SERVICE 将失败。这个额外的 EAI 要求仅适用于 oauth2 类型的密钥,而不适用于其他类型的密钥。

总之,创建此类服务的一般步骤是:

  1. 创建 oauth2 类型的密钥(如前所示)。

  2. 创建 EAI 以允许服务使用密钥。例如:

    CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION example_eai
      ALLOWED_NETWORK_RULES = (<name>)
      ALLOWED_AUTHENTICATION_SECRETS = (testdb.testschema.oauth_secret)
      ENABLED = true;
    
    Copy
  3. 创建规范中包含 containers.secrets 字段的服务。这也会指定可选的 EXTERNAL_ACCESS_INTEGRATIONS 属性以纳入 EAI,从而允许使用 oauth2 密钥。

    一个示例 CREATE SERVICE(带内联规范)命令:

    CREATE SERVICE eai_service
      IN COMPUTE POOL MYPOOL
      EXTERNAL_ACCESS_INTEGRATIONS = (example_eai)
      FROM SPECIFICATION
      $$
      spec:
        containers:
          - name: main
            image: <url>
            secrets:
            - snowflakeSecret: testdb.testschema.oauth_secret
              directoryPath: '/usr/local/creds'
        endpoints:
          - name: api
            port: 8080
      $$;
    
    Copy

有关出口的更多信息,请参阅 配置网络出口

准则和限制

  • 一般限制: 如果您在这些限制方面遇到任何问题,请联系您的账户代表。

    • Snowflake 账户中最多可创建 200 项服务。

    • 每项服务最多可公开 100 个端点。

    • 如果您使用外部访问集成 (EAIs) 启用互联网访问(请参阅 配置网络出口),则适用以下限制。

      • 每项服务最多可支持 10 个 EAIs(请参阅 CREATE SERVICEALTER SERVICE)。

      • 每个 EAI 最多可以有 100 个主机名。

    • Snowpark Container Services 支持 AWS 和 Azure 上的 出站专用连接入站专用连接 在 AWS 中仍处于预览模式。如果需要 Azure 上的入站专用连接,请与客户代表联系。

    • 当从互联网访问公共端点时,您可能会发现用户名/密码身份验证正常,但 SSO 导致空白页或错误:“OAuth client integration with the given client ID is not found.”。有关解决此问题的信息,请参阅 入口和 SSO 注意事项

  • 镜像平台要求: 目前,Snowpark Container Services 需要 linux/amd64 平台镜像。

  • 服务容器没有特权: 服务容器不以特权身份运行,因此无法更改主机上的硬件配置,只能更改有限的 OS 配置。服务容器只能执行普通用户(即不需要 root 的用户)可以执行的操作系统配置。

  • 重命名数据库和架构:

    • 请勿重命名已创建服务的数据库和架构。重命名实际上是将服务移动到另一个数据库和架构,系统不支持此操作。例如:

      • 服务 DNS 名称将继续反映旧的数据库和架构名称。

      • Snowflake 提供给正在运行的服务容器的数据库和架构信息将继续引用旧名称。

      • 服务在事件表中引入的新日志将继续引用旧的数据库和架构名称。

      • 服务函数将继续引用旧数据库和架构中的服务,当您调用服务函数时,它将失败。

    • 服务规范可以引用 Snowflake 暂存区和镜像仓库等对象。如果重命名这些对象所在的数据库或架构名称,则需要手动更新服务规范中引用的对象的数据库和架构的名称。

  • 删除和取消删除数据库和架构:

    • 删除父数据库或架构时,将异步删除服务。这意味着在内部进程移除服务之前,服务可能会继续运行一段时间。

    • 如果尝试取消删除以前删除的数据库或架构,则无法保证会还原服务。

  • 所有权转让: 不支持转让服务(包括工作服务)的所有权。

  • 复制: 在 Snowflake 中处理复制时,请注意以下事项:

    • Snowpark Container Services 对象(例如服务、计算池和镜像仓库)无法复制。

    • 如果在数据库中创建镜像仓库,则整个数据库都无法复制。如果数据库包含其他资源(如服务或计算池),则数据库复制过程将成功,但不会复制数据库中的这些单个对象。

  • 作业服务超时: Snowpark Container Services 作业服务同步运行。如果语句超时,则取消作业服务。默认语句超时为两天。客户可以通过使用 ALTER SESSION 设置参数 STATEMENT_TIMEOUT_IN_SECONDS 来更改超时。

    ALTER SESSION SET statement_timeout_in_seconds=<time>
    
    Copy

    请在运行 EXECUTE JOB SERVICE 命令前进行该设置。

语言: 中文