【问题标题】:Snowpipe rest api returning always "invalid jwt token"Snowpipe rest api 总是返回“无效的 jwt 令牌”
【发布时间】:2021-05-07 01:42:55
【问题描述】:

我正在尝试使用 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

我还尝试了其他 python 代码(和其他代码),但出现相同的错误:
https://docs.snowflake.com/en/user-guide/data-load-snowpipe-rest-load.html#sample-program-for-the-python-sdk

【问题讨论】:

    标签: snowflake-cloud-data-platform snowflake-pipe


    【解决方案1】:

    我也遇到了这个问题,想从 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 Security Manager 的 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 替换标题。

    【讨论】:

      猜你喜欢
      • 2020-05-05
      • 2019-05-09
      • 2019-05-19
      • 2019-11-20
      • 2016-09-18
      • 2015-04-29
      • 1970-01-01
      • 2021-04-13
      • 2016-06-27
      相关资源
      最近更新 更多