1

我正在尝试使用 Snowflake 站点中指出的 Snowpipe rest api:
https ://docs.snowflake.com/en/user-guide/data-load-snowpipe-rest-apis.html#data-file-ingestion

我在这里找到了一个 python 示例,我的代码和步骤几乎相同:
https ://community.snowflake.com/s/article/Connect-to-Snowpipe-Rest-API-by-using-JWT-Token

我检查了https://jwt.io/#debugger中的令牌,它是一个有效的 jwt 令牌。

然而,Snowpipe api 总是响应:

{
  "code": "390144",
  "data": null,
  "message": "JWT token is invalid.",
  "success": false,
  "headers": null
}

我错过了什么吗?

我使用以下步骤创建了密钥:
https ://docs.snowflake.com/en/user-guide/data-load-snowpipe-rest-gs.html#step-3-configure-security-per-user

我也试过thise其他python代码(和其他的),但有同样的错误:
https ://docs.snowflake.com/en/user-guide/data-load-snowpipe-rest-load.html#sample-program -for-the-python-sdk

4

1 回答 1

2

我也遇到了这个问题,想从 Go 调用 REST API,所以需要转换 Python 脚本。我遇到的一个问题是标准 Go 库不支持加密的 PKCS 8 密钥,我实际上建议在没有加密密钥的情况下开始以消除额外的障碍。我了解您从文档中完成了这些步骤,但我将逐步将它们放入其中。

生成密钥(未加密)

$ openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt
$ cat rsa_key.p8
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChSSRI8qUHxvoe
TME1CQuUtGWQ+a2esAZ/yOaaVbGpAo8B3X8....
$ openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
$ cat rsa_key.pub
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoUkkSPKlB8b6HkzBNQkL
lLRlkPmtnrAGf8jmmlWxqQKPAd1/Aw+kn....

更新雪花用户

为用户设置公钥,只有密钥本身删除第一行和最后一行

ALTER USER MY_USER SET rsa_public_key='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoUkkSPKlB8b6HkzBNQkL
lLRlkPmtnrAGf8jmmlWxqQKPAd1/Aw+kn....';

获取更新后存储在用户身上的指纹。

DESCRIBE USER MY_USER;

找到属性RSA_PUBLIC_KEY_FP并复制值。这将用于创建 JWT。

SHA256:+Uys1...

创建指纹

可以在此处找到用于创建此指纹的 Python 代码。 https://github.com/snowflakedb/snowflake-ingest-python/blob/master/snowflake/ingest/utils/tokentools.py#L108

这对我来说是一个很大的起点,我想确保我可以创建与 Snowflake 中的指纹相匹配的指纹。这是我在 Go 中的代码和测试。

import (
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "github.com/pkg/errors"
)

func calculatePublicKeyFingerprint(privateKey string) (string, error) {
    pemBlock, _ := pem.Decode([]byte(privateKey))
    parsedKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
    if err != nil {
        return "", errors.Wrap(err, "parse error")
    }

    var privKey *rsa.PrivateKey
    var ok bool
    if privKey, ok = parsedKey.(*rsa.PrivateKey); !ok {
        return "", errors.New("Unable to parse RSA private key")
    }

    pubKey := privKey.Public().(*rsa.PublicKey)
    pubDER, err := x509.MarshalPKIXPublicKey(pubKey)
    if err != nil {
        return "", err
    }

    hasher := sha256.New()
    hasher.Write(pubDER)
    shaB64encoded := base64.StdEncoding.EncodeToString(hasher.Sum(nil))

    return "SHA256:" + shaB64encoded, nil
}

单元测试 - Snowflake 的私钥和预期指纹已被截断,使用您的完整值。还使用 Testify 进行断言。

const privateKey = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChSSRI8qUHxvoe
TME1CQuUtGWQ+a2esAZ/yOaaVbGpAo8B3X8....`

func Test_calculatePublicKeyFingerprint(t *testing.T) {
    gotFingerprint, err := calculatePublicKeyFingerprint(privateKey)
    require.NoError(t, err)
    fromSnowflake := "SHA256:+Uys1..."
    require.Equal(t, fromSnowflake, gotFingerprint)
}

创建 JWT

Python 安全管理器的 get_token 部分调用指纹创建并返回 JWT。

这是我的 Go 代码,它不像 Python 代码那样检查当前标记,这并不奇怪。它每次都会根据您的 Snowflake 帐户、用户和私钥创建一个新令牌。

import (
    "github.com/dgrijalva/jwt-go"
)

const (
    issuer         = "iss"
    expireTime     = "exp"
    issueTime      = "iat"
    subject        = "sub"
    expireDuration = time.Hour
)

func CreateJWT(account, user, privateKey string) (string, error) {
    qualifiedUsername := strings.ToUpper(account + "." + user)
    publicKeyFp, err := calculatePublicKeyFingerprint(privateKey)
    if err != nil {
        return "", err
    }

    claims := jwt.MapClaims{
        issuer:     qualifiedUsername + "." + publicKeyFp,
        subject:    qualifiedUsername,
        issueTime:  time.Now().Unix(),
        expireTime: time.Now().Add(expireDuration).Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
    pk, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
    if err != nil {
        return "", err
    }

    return token.SignedString(pk)
}

为了清楚起见,我的声明看起来像

claims = jwt.MapClaims{
        "iss": "MYSFACCT.MY_USER.SHA256:+Uys1...",
        "sub": "MYSFACCT.MY_USER",
        "iat": 1620322087,
        "exp": 1620325687,
    }

发出请求

curl --location --request POST 'https://mysfacct.snowflakecomputing.com/v1/data/pipes/MY_DATABASE.MY_SCHEMA.MY_PIPE/insertFiles' \
--header 'Authorization: Bearer generated.JWT.from-CreateJWT(account, user, privatekey)' \
--header 'Content-Type: application/json' \
--data-raw '{"files":[{"path":"my-file.json"}]}'

确保将 URL 替换为您的帐户和完全合格的管道。还将标题替换为您生成的 JWT。

于 2021-05-06T17:42:55.870 回答