golang Travis CI Webhooks签名验证

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang Travis CI Webhooks签名验证相关的知识,希望对你有一定的参考价值。

package main

/*

Copyright 2017 Shapath Neupane (@theshapguy)

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

------------------------------------------------

Listner - written in Go because it's native web server is much more robust than Python. Plus its fun to write Go!

NOTE: Make sure you are using the right domain for travis [.com] or [.org]

*/

import (
	"crypto"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
)

var logPrint = log.Println

type ConfigKey struct {
	Config struct {
		Host        string `json:"host"`
		ShortenHost string `json:"shorten_host"`
		Assets      struct {
			Host string `json:"host"`
		} `json:"assets"`
		Pusher struct {
			Key string `json:"key"`
		} `json:"pusher"`
		Github struct {
			APIURL string   `json:"api_url"`
			Scopes []string `json:"scopes"`
		} `json:"github"`
		Notifications struct {
			Webhook struct {
				PublicKey string `json:"public_key"`
			} `json:"webhook"`
		} `json:"notifications"`
	} `json:"config"`
}

func PayloadSignature(r *http.Request) ([]byte, error) {

	signature := r.Header.Get("Signature")
	b64, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return nil, errors.New("cannot decode signature")
	}

	return b64, nil
}

func parsePublicKey(key string) (*rsa.PublicKey, error) {

	// https://golang.org/pkg/encoding/pem/#Block
	block, _ := pem.Decode([]byte(key))

	if block == nil || block.Type != "PUBLIC KEY" {
		return nil, errors.New("invalid public key")
	}

	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, errors.New("invalid public key")
	}

	return publicKey.(*rsa.PublicKey), nil

}

func TravisPublicKey() (*rsa.PublicKey, error) {
	// NOTE: Use """https://api.travis-ci.com/config""" for private repos.
	response, err := http.Get("https://api.travis-ci.org/config")

	if err != nil {
		return nil, errors.New("cannot fetch travis public key")
	}
	defer response.Body.Close()

	decoder := json.NewDecoder(response.Body)
	var t ConfigKey
	err = decoder.Decode(&t)
	if err != nil {
		return nil, errors.New("cannot decode travis public key")
	}

	key, err := parsePublicKey(t.Config.Notifications.Webhook.PublicKey)
	if err != nil {
		return nil, err
	}

	return key, nil

}

func PayloadDigest(payload string) []byte {
	hash := sha1.New()
	hash.Write([]byte(payload))
	return hash.Sum(nil)

}

func RespondWithError(w http.ResponseWriter, m string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(401)
	message := fmt.Sprintf("{\"message\": \"%s\"}", m)
	w.Write([]byte(message))
}

func RespondWithSuccess(w http.ResponseWriter, m string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	message := fmt.Sprintf("{\"message\": \"%s\"}", m)
	w.Write([]byte(message))
}

func DeployHandler(w http.ResponseWriter, r *http.Request) {

	key, err := TravisPublicKey()
	if err != nil {
		RespondWithError(w, err.Error())
		return
	}
	signature, err := PayloadSignature(r)
	if err != nil {
		RespondWithError(w, err.Error())
		return
	}
	payload := PayloadDigest(r.FormValue("payload"))

	err = rsa.VerifyPKCS1v15(key, crypto.SHA1, payload, signature)

	if err != nil {
		RespondWithError(w, errors.New("unauthorized payload").Error())
		return
	}

	RespondWithSuccess(w, "payload verified")

}

func main() {

	http.HandleFunc("/", DeployHandler)
	log.Fatal(http.ListenAndServe(":5000", nil))
}

Travis-Ci 的 pypi 部署如何安全?

【中文标题】Travis-Ci 的 pypi 部署如何安全?【英文标题】:How is Travis-Ci's pypi deployment secure? 【发布时间】:2018-09-07 09:29:44 【问题描述】:

使用travis-ci部署到pypi时,流程为:

    设置 travis-ci 帐户并连接存储库。

    在该存储库中,包含如下所示的 pypi 部署:

    provider: pypi
    
    user: "PYPI_USER"
    
    password:
    
      secure: "PYPI_PASSWORD_SECURED_WITH_TRAVIS_ENCRYPT"
    
    on:
    
      tags: true
    

这个安全模型是如何工作的?为什么别人不能复制我的用户和密码并部署到那个pip包?

【问题讨论】:

【参考方案1】:

这个安全模型是如何工作的?

足够好。 (你还没有和我签订合同,所以我不能给任何承诺。)

为什么别人不能复制我的用户和密码并部署到那个 pip 包?

因为她无法解密你加密的秘密值。 For each registered repository, Travis CI generates an RSA keypair——也就是说,被你的公钥加密的值不能被入侵者的私钥解密,只有你的私钥可以解密,而且私钥只能被 Travis CI 访问。

【讨论】:

哦,我没有意识到它正在从目录内部访问一个 pubkey。这就说得通了。谢谢。

以上是关于golang Travis CI Webhooks签名验证的主要内容,如果未能解决你的问题,请参考以下文章

golang 用golang处理Github webhooks

GitHub项目加入Travis-CI的自动集成

travis.ci 上的 Android 构建变体

Travis CI电子邮件通知主题

Travis-Ci 的 pypi 部署如何安全?

Travis-CI 在提交期间给出错误