It only has Topic Publish.
This is most likely because SDK needs to use websocket protocol to get bidirectional communication.
Even if this is closed as "Will not implement", this should stand as notice API is incomplete compared with JS and Java SDKs feature set.
Also Note in source file
service/iotdataplane/service.go
We see comment:
// AWS IoT-Data enables secure, bi-directional communication between Internet-connected
// things (such as sensors, actuators, embedded devices, or smart appliances)
While AWS IoT API is bi-direction capable, the AWS IoT Golang SDK is not bi-directional
Hello @dhubler, thank you for reaching out to us. Those are service specific SDKs, the JS and Java SDK, and currently are only supported by those two SDKs. This would be a great feature and I will mark this as a feature request.
As a work around, If anyone is interested, I followed instructions in
http://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#http
ported code to golang and using paho MQTT over websockets protocol from Golang SDK.
func AwsIotWsUrl(accessKey string, secretKey string, region string, endpoint string) string {
host := fmt.Sprintf("%s.iot.%s.amazonaws.com", endpoint, region)
// according to docs, time must be within 5min of actual time (or at least according to AWS servers)
now := time.Now().UTC()
dateLong := now.Format("20060102T150405Z")
dateShort := dateLong[:8]
serviceName := "iotdevicegateway"
scope := fmt.Sprintf("%s/%s/%s/aws4_request", dateShort, region, serviceName)
alg := "AWS4-HMAC-SHA256"
q := [][2]string{
{"X-Amz-Algorithm", alg},
{"X-Amz-Credential", accessKey + "/" + scope},
{"X-Amz-Date", dateLong},
{"X-Amz-SignedHeaders", "host"},
}
query := awsQueryParams(q)
signKey := awsSignKey(secretKey, dateShort, region, serviceName)
stringToSign := awsSignString(accessKey, secretKey, query, host, dateLong, alg, scope)
signature := fmt.Sprintf("%x", awsHmac(signKey, []byte(stringToSign)))
return fmt.Sprintf("wss://%s/mqtt?%s&X-Amz-Signature=%s", host, query, signature)
}
func awsQueryParams(q [][2]string) string {
var buff bytes.Buffer
var i int
for _, param := range q {
if i != 0 {
buff.WriteRune('&')
}
i++
buff.WriteString(param[0])
buff.WriteRune('=')
buff.WriteString(url.QueryEscape(param[1]))
}
return buff.String()
}
func awsSignString(accessKey string, secretKey string, query string, host string, dateLongStr string, alg string, scopeStr string) string {
emptyStringHash := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
req := strings.Join([]string{
"GET",
"/mqtt",
query,
"host:" + host,
"", // separator
"host",
emptyStringHash,
}, "\n")
return strings.Join([]string{
alg,
dateLongStr,
scopeStr,
awsSha(req),
}, "\n")
}
func awsHmac(key []byte, data []byte) []byte {
h := hmac.New(sha256.New, key)
h.Write(data)
return h.Sum(nil)
}
func awsSignKey(secretKey string, dateShort string, region string, serviceName string) []byte {
h := awsHmac([]byte("AWS4"+secretKey), []byte(dateShort))
h = awsHmac(h, []byte(region))
h = awsHmac(h, []byte(serviceName))
h = awsHmac(h, []byte("aws4_request"))
return h
}
func awsSha(in string) string {
h := sha256.New()
fmt.Fprintf(h, "%s", in)
return fmt.Sprintf("%x", h.Sum(nil))
}
+1 this, we're really hurting not being able to pubsub from iot topics
FWIW I made a library to fill the gap until the AWS SDK provides support for this: https://github.com/glassechidna/awsiot
sess := session.Must(session.NewSessionWithOptions(sessOpts))
iot := awsiot.New(sess)
theUrl, _ := iot.WebsocketUrl("a1kxjqeyezkt7") // can be used with the eclipse paho mqtt library
Just a note, as it took me a while to figure this out. It seems that @dhubler and @aidansteele solutions don't work (anymore?). IOT seems to want the X-Amz-Security-Token parameter, but it cannot be part of the canonical query parameters. It has to be added on after signing.
@swt2c I'll check that out. I had it working a couple of weeks ago, haven't tried since.
@swt2c @aidansteele Did you got this working? i've used @dhubler solution and appended the securityToken.. but i still get a 403 ..
Yes, I did get it working at the time. You included the security token outside of the signature?
@swt2c yes, did you use @dhubler example of the library by @aidansteele ?
Yes, but unfortunately, I'm not using it anymore and don't have access to the code I was using.
One thing to know even if you get this working, websocket connection will
reset every 24 hours. Ultimately using straight MQTT and TLS was most
reliable.
On Mon, Jul 2, 2018, 8:36 AM Scott Talbert notifications@github.com wrote:
Yes, but unfortunately, I'm not using it anymore and don't have access to
the code I was using.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/aws/aws-sdk-go/issues/820#issuecomment-401845527, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAAgfip3PIZCg3P6uWC6cA8Jl1WVDpk7ks5uCj3ugaJpZM4Jwdvx
.
The solution provided by @aidansteele is still working great.
Tested just now
I just had a hard time to get this working and want to share what I ended up doing.
// usage:
// addr, err := AwsIotWsUrl(sess, "xxxxx-ats.iot.eu-west-1.amazonaws.com ")
func AwsIotWsUrl(p client.ConfigProvider, endpoint string) (string, error) {
serviceName := "iotdevicegateway"
config := p.ClientConfig(serviceName)
region := *config.Config.Region
creds, err := config.Config.Credentials.Get()
if err != nil {
return "", err
}
accessKey := creds.AccessKeyID
secretKey := creds.SecretAccessKey
sessionToken := creds.SessionToken
// according to docs, time must be within 5min of actual time (or at least according to AWS servers)
now := time.Now().UTC()
dateLong := now.Format("20060102T150405Z")
dateShort := dateLong[:8]
scope := fmt.Sprintf("%s/%s/%s/aws4_request", dateShort, region, serviceName)
alg := "AWS4-HMAC-SHA256"
q := [][2]string{
{"X-Amz-Algorithm", alg},
{"X-Amz-Credential", accessKey + "/" + scope},
{"X-Amz-Date", dateLong},
{"X-Amz-SignedHeaders", "host"},
}
query := awsQueryParams(q)
signKey := awsSignKey(secretKey, dateShort, region, serviceName)
stringToSign := awsSignString(accessKey, secretKey, query, endpoint, dateLong, alg, scope)
signature := fmt.Sprintf("%x", awsHmac(signKey, []byte(stringToSign)))
return fmt.Sprintf("wss://%s/mqtt?%s&X-Amz-Signature=%s&X-Amz-Security-Token=%s", endpoint, query, signature, url.QueryEscape(sessionToken)), nil
}
func awsQueryParams(q [][2]string) string {
var buff bytes.Buffer
var i int
for _, param := range q {
if i != 0 {
buff.WriteRune('&')
}
i++
buff.WriteString(param[0])
buff.WriteRune('=')
buff.WriteString(url.QueryEscape(param[1]))
}
return buff.String()
}
func awsSignString(accessKey string, secretKey string, query string, host string, dateLongStr string, alg string, scopeStr string) string {
emptyStringHash := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
req := strings.Join([]string{
"GET",
"/mqtt",
query,
"host:" + host,
"", // separator
"host",
emptyStringHash,
}, "\n")
return strings.Join([]string{
alg,
dateLongStr,
scopeStr,
awsSha(req),
}, "\n")
}
func awsHmac(key []byte, data []byte) []byte {
h := hmac.New(sha256.New, key)
h.Write(data)
return h.Sum(nil)
}
func awsSignKey(secretKey string, dateShort string, region string, serviceName string) []byte {
h := awsHmac([]byte("AWS4"+secretKey), []byte(dateShort))
h = awsHmac(h, []byte(region))
h = awsHmac(h, []byte(serviceName))
h = awsHmac(h, []byte("aws4_request"))
return h
}
func awsSha(in string) string {
h := sha256.New()
fmt.Fprintf(h, "%s", in)
return fmt.Sprintf("%x", h.Sum(nil))
}
Most helpful comment
As a work around, If anyone is interested, I followed instructions in
http://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#http
ported code to golang and using paho MQTT over websockets protocol from Golang SDK.