슬랙에서는 어떻게 인증처리를 할까?
아래는 Slack Slash Command 기능을 활용한 슬랙 봇의 인증 부분에 관한 설명이에요. 인하우스 용도로 사용되는 슬랙 봇은 조직에 속한 구성원들만 사용할 수 있기에 별도의 확인 절차를 거치지 않고 인증처리를 할 수 있기에 편리하게 사용할 수 있어요.
그럼 본론으로 돌아가서 슬랙에서는 인증처리를 어떻게 수행할까요?
먼저 코드를 보면 인증 부분에 대한 로직은 아래와 같아요.
type SlackVerifier interface {
VerifySignature(c *gin.Context, SlackSigningSecretByte []byte) bool
checkTimestamp(request time.Time, standard time.Time) bool
checkMac(clientSlackSigningSecret string, sig string, SlackSigningSecretByte []byte) bool
}
type SlackRequestVerifier struct{}
var _ SlackVerifier = (*SlackRequestVerifier)(nil)
// Authenticate is a function to authenticate the request from Slack
func Authenticate(c *gin.Context) bool {
SlackRequestVerifier := SlackRequestVerifier{}
if !SlackRequestVerifier.VerifySignature(c, []byte(configs.SLACK_SIGNING_SECRET)) {
logrus.WithFields(logrus.Fields{
"client_ip": c.ClientIP(),
}).Warn("failed to verify signature: Authenticate")
return false
}
return true
}
// VerifySignature is a function to verify the request from Slack
func (srv *SlackRequestVerifier) VerifySignature(c *gin.Context, SlackSigningSecretByte []byte) bool {
// Extract the request body
var bodyBytes []byte
if c.Request.Body != nil {
bodyBytes, _ = io.ReadAll(c.Request.Body)
} else {
logrus.Error("Request body is nil: VerifySignature")
return false
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
bString := string(bodyBytes)
// Extract the timestamp and signature
ts := c.GetHeader("X-Slack-Request-Timestamp")
sig := c.GetHeader("X-Slack-Signature")
tsInt64, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
logrus.WithFields(logrus.Fields{
"tsInt64": tsInt64,
}).WithError(err).Warn("failed to parse timestamp: VerifySignature")
return false
}
requestTime := time.Unix(tsInt64, 0)
currentTime := time.Now()
if !srv.checkTimestamp(requestTime, currentTime) {
logrus.Warn("failed to check timestamp: VerifySignature")
return false
}
version := "v0"
calculatedSigningSecret := version + ":" + ts + ":" + bString
return srv.checkMac(calculatedSigningSecret, sig, SlackSigningSecretByte)
}
// checkTimestamp is a function to check between the request time and the current time with a 5-minute difference
// if the difference is greater than 5 minutes, return false
func (srv *SlackRequestVerifier) checkTimestamp(request time.Time, standard time.Time) bool {
timeDiff := standard.Sub(request)
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
return false
}
return true
}
// checkMac is a function to check the signature
func (srv *SlackRequestVerifier) checkMac(
calculatedSigningSecret string, /* Calculated Signing Secret (version + timestamp + body) */
sig string, /* HTTP Header (X-Slack-Signature) */
SlackSigningSecretByte []byte, /* Slack Signing Secret */
) bool {
// Create a new HMAC by passing the SHA-256 hash algorithm and the Slack Signing Secret
mac := hmac.New(sha256.New, SlackSigningSecretByte)
// Write the calculated signing secret to the HMAC
if _, err := mac.Write([]byte(calculatedSigningSecret)); err != nil {
logrus.WithError(err).Warn("failed to write mac: checkMac")
return false
}
// Calculate the MAC
originalMac := "v0=" + hex.EncodeToString(mac.Sum(nil))
// Compare the calculated MAC with the original MAC
return hmac.Equal([]byte(originalMac), []byte(sig))
}
개요
- Slack은 Slash Command를 사용하여 API 서버에 요청을 보낼 때 사용자의 요청이 인증된 사용자가 수행한 액션인지 Signature를 계산하여 확인한다.
- Slack을 통해 API 서버에 전달하는 요청의 HTTP 헤더에는 X-Slack-Signature이 포함되며 대소문자를 구분하지 않는다. 서명 계산 방식은 먼저 요청 본문을 SHA-256 함수로 해싱하고 이를 HMAC Signing secret (SLACK_SIGNING_SECRET)과 결합한 다.
- 이러한 계산 방식으로 요청마다 씨크릿 정보를 포함하지 않고서 앱의 보안을 유지할 수 있다.
Pattern
- slack-endpoint-server가 Slack으로부터 요청을 받는다.
- slack-endpoint-server에서 요청을 기반으로 서명을 계산합니다.
- 계산한 서명이 요청의 서명과 일치하는지 확인합니다.
자세히
- Slack Signing Secret을 관리자로부터 받습니다.
- Request에서 타임스탬프 헤더를 추출합니다.
- 서버에서 요청을 확인한 현재 시간과 Client에서 요청한 시간(Request 타임스탬프 헤더)을 서로 비교하고 만약 5분 이상 차이가 난다면 인증 불가 처리를 합니다.
- 콜론(:)을 구분 기호로 사용하여 버전 번호, 타임스탬프, 요청 본문을 합쳐 하나의 문자열로 생성합니다.
- 1에서 관리자로부터 받은 Slack Signing Secret을 사용하여 하나로 합친 문자열을 해시하고 해시의 16진수 다이제스트를 가져옵니다.
- 해시의 16진수 다이제스트와 Request의 헤더 X-Slack-Signature과 비교합니다.
- 서로 같으면 인증을 통과시키고 그렇지 않으면 인증을 통과시키지 않습니다.