cloudwatch.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package cloudwatch
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "io/ioutil"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/aws/aws-sdk-go/aws"
  10. "github.com/aws/aws-sdk-go/aws/awsutil"
  11. "github.com/aws/aws-sdk-go/aws/credentials"
  12. "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
  13. "github.com/aws/aws-sdk-go/aws/ec2metadata"
  14. "github.com/aws/aws-sdk-go/aws/session"
  15. "github.com/aws/aws-sdk-go/service/cloudwatch"
  16. "github.com/aws/aws-sdk-go/service/ec2"
  17. "github.com/aws/aws-sdk-go/service/sts"
  18. "github.com/grafana/grafana/pkg/log"
  19. "github.com/grafana/grafana/pkg/middleware"
  20. m "github.com/grafana/grafana/pkg/models"
  21. )
  22. type actionHandler func(*cwRequest, *middleware.Context)
  23. var actionHandlers map[string]actionHandler
  24. type cwRequest struct {
  25. Region string `json:"region"`
  26. Action string `json:"action"`
  27. Body []byte `json:"-"`
  28. DataSource *m.DataSource
  29. }
  30. func init() {
  31. actionHandlers = map[string]actionHandler{
  32. "GetMetricStatistics": handleGetMetricStatistics,
  33. "ListMetrics": handleListMetrics,
  34. "DescribeAlarms": handleDescribeAlarms,
  35. "DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
  36. "DescribeAlarmHistory": handleDescribeAlarmHistory,
  37. "DescribeInstances": handleDescribeInstances,
  38. "__GetRegions": handleGetRegions,
  39. "__GetNamespaces": handleGetNamespaces,
  40. "__GetMetrics": handleGetMetrics,
  41. "__GetDimensions": handleGetDimensions,
  42. }
  43. }
  44. type cache struct {
  45. credential *credentials.Credentials
  46. expiration *time.Time
  47. }
  48. var awsCredentialCache map[string]cache = make(map[string]cache)
  49. var credentialCacheLock sync.RWMutex
  50. func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials {
  51. credentialCacheLock.RLock()
  52. if _, ok := awsCredentialCache[profile]; ok {
  53. if awsCredentialCache[profile].expiration != nil &&
  54. (*awsCredentialCache[profile].expiration).After(time.Now().UTC()) {
  55. result := awsCredentialCache[profile].credential
  56. credentialCacheLock.RUnlock()
  57. return result
  58. }
  59. }
  60. credentialCacheLock.RUnlock()
  61. accessKeyId := ""
  62. secretAccessKey := ""
  63. sessionToken := ""
  64. var expiration *time.Time
  65. expiration = nil
  66. if strings.Index(assumeRoleArn, "arn:aws:iam:") == 0 {
  67. params := &sts.AssumeRoleInput{
  68. RoleArn: aws.String(assumeRoleArn),
  69. RoleSessionName: aws.String("GrafanaSession"),
  70. DurationSeconds: aws.Int64(900),
  71. }
  72. stsSess := session.New()
  73. stsCreds := credentials.NewChainCredentials(
  74. []credentials.Provider{
  75. &credentials.EnvProvider{},
  76. &credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
  77. &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(stsSess), ExpiryWindow: 5 * time.Minute},
  78. })
  79. stsConfig := &aws.Config{
  80. Region: aws.String(region),
  81. Credentials: stsCreds,
  82. }
  83. svc := sts.New(session.New(stsConfig), stsConfig)
  84. resp, err := svc.AssumeRole(params)
  85. if err != nil {
  86. // ignore
  87. log.Error(3, "CloudWatch: Failed to assume role", err)
  88. }
  89. if resp.Credentials != nil {
  90. accessKeyId = *resp.Credentials.AccessKeyId
  91. secretAccessKey = *resp.Credentials.SecretAccessKey
  92. sessionToken = *resp.Credentials.SessionToken
  93. expiration = resp.Credentials.Expiration
  94. }
  95. }
  96. sess := session.New()
  97. creds := credentials.NewChainCredentials(
  98. []credentials.Provider{
  99. &credentials.StaticProvider{Value: credentials.Value{
  100. AccessKeyID: accessKeyId,
  101. SecretAccessKey: secretAccessKey,
  102. SessionToken: sessionToken,
  103. }},
  104. &credentials.EnvProvider{},
  105. &credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
  106. &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
  107. })
  108. credentialCacheLock.Lock()
  109. awsCredentialCache[profile] = cache{
  110. credential: creds,
  111. expiration: expiration,
  112. }
  113. credentialCacheLock.Unlock()
  114. return creds
  115. }
  116. func getAwsConfig(req *cwRequest) *aws.Config {
  117. assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
  118. cfg := &aws.Config{
  119. Region: aws.String(req.Region),
  120. Credentials: getCredentials(req.DataSource.Database, req.Region, assumeRoleArn),
  121. }
  122. return cfg
  123. }
  124. func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
  125. cfg := getAwsConfig(req)
  126. svc := cloudwatch.New(session.New(cfg), cfg)
  127. reqParam := &struct {
  128. Parameters struct {
  129. Namespace string `json:"namespace"`
  130. MetricName string `json:"metricName"`
  131. Dimensions []*cloudwatch.Dimension `json:"dimensions"`
  132. Statistics []*string `json:"statistics"`
  133. StartTime int64 `json:"startTime"`
  134. EndTime int64 `json:"endTime"`
  135. Period int64 `json:"period"`
  136. } `json:"parameters"`
  137. }{}
  138. json.Unmarshal(req.Body, reqParam)
  139. params := &cloudwatch.GetMetricStatisticsInput{
  140. Namespace: aws.String(reqParam.Parameters.Namespace),
  141. MetricName: aws.String(reqParam.Parameters.MetricName),
  142. Dimensions: reqParam.Parameters.Dimensions,
  143. Statistics: reqParam.Parameters.Statistics,
  144. StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
  145. EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
  146. Period: aws.Int64(reqParam.Parameters.Period),
  147. }
  148. resp, err := svc.GetMetricStatistics(params)
  149. if err != nil {
  150. c.JsonApiErr(500, "Unable to call AWS API", err)
  151. return
  152. }
  153. c.JSON(200, resp)
  154. }
  155. func handleListMetrics(req *cwRequest, c *middleware.Context) {
  156. cfg := getAwsConfig(req)
  157. svc := cloudwatch.New(session.New(cfg), cfg)
  158. reqParam := &struct {
  159. Parameters struct {
  160. Namespace string `json:"namespace"`
  161. MetricName string `json:"metricName"`
  162. Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"`
  163. } `json:"parameters"`
  164. }{}
  165. json.Unmarshal(req.Body, reqParam)
  166. params := &cloudwatch.ListMetricsInput{
  167. Namespace: aws.String(reqParam.Parameters.Namespace),
  168. MetricName: aws.String(reqParam.Parameters.MetricName),
  169. Dimensions: reqParam.Parameters.Dimensions,
  170. }
  171. var resp cloudwatch.ListMetricsOutput
  172. err := svc.ListMetricsPages(params,
  173. func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
  174. metrics, _ := awsutil.ValuesAtPath(page, "Metrics")
  175. for _, metric := range metrics {
  176. resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric))
  177. }
  178. return !lastPage
  179. })
  180. if err != nil {
  181. c.JsonApiErr(500, "Unable to call AWS API", err)
  182. return
  183. }
  184. c.JSON(200, resp)
  185. }
  186. func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
  187. cfg := getAwsConfig(req)
  188. svc := cloudwatch.New(session.New(cfg), cfg)
  189. reqParam := &struct {
  190. Parameters struct {
  191. ActionPrefix string `json:"actionPrefix"`
  192. AlarmNamePrefix string `json:"alarmNamePrefix"`
  193. AlarmNames []*string `json:"alarmNames"`
  194. StateValue string `json:"stateValue"`
  195. } `json:"parameters"`
  196. }{}
  197. json.Unmarshal(req.Body, reqParam)
  198. params := &cloudwatch.DescribeAlarmsInput{
  199. MaxRecords: aws.Int64(100),
  200. }
  201. if reqParam.Parameters.ActionPrefix != "" {
  202. params.ActionPrefix = aws.String(reqParam.Parameters.ActionPrefix)
  203. }
  204. if reqParam.Parameters.AlarmNamePrefix != "" {
  205. params.AlarmNamePrefix = aws.String(reqParam.Parameters.AlarmNamePrefix)
  206. }
  207. if len(reqParam.Parameters.AlarmNames) != 0 {
  208. params.AlarmNames = reqParam.Parameters.AlarmNames
  209. }
  210. if reqParam.Parameters.StateValue != "" {
  211. params.StateValue = aws.String(reqParam.Parameters.StateValue)
  212. }
  213. resp, err := svc.DescribeAlarms(params)
  214. if err != nil {
  215. c.JsonApiErr(500, "Unable to call AWS API", err)
  216. return
  217. }
  218. c.JSON(200, resp)
  219. }
  220. func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
  221. cfg := getAwsConfig(req)
  222. svc := cloudwatch.New(session.New(cfg), cfg)
  223. reqParam := &struct {
  224. Parameters struct {
  225. Namespace string `json:"namespace"`
  226. MetricName string `json:"metricName"`
  227. Dimensions []*cloudwatch.Dimension `json:"dimensions"`
  228. Statistic string `json:"statistic"`
  229. Period int64 `json:"period"`
  230. } `json:"parameters"`
  231. }{}
  232. json.Unmarshal(req.Body, reqParam)
  233. params := &cloudwatch.DescribeAlarmsForMetricInput{
  234. Namespace: aws.String(reqParam.Parameters.Namespace),
  235. MetricName: aws.String(reqParam.Parameters.MetricName),
  236. Period: aws.Int64(reqParam.Parameters.Period),
  237. }
  238. if len(reqParam.Parameters.Dimensions) != 0 {
  239. params.Dimensions = reqParam.Parameters.Dimensions
  240. }
  241. if reqParam.Parameters.Statistic != "" {
  242. params.Statistic = aws.String(reqParam.Parameters.Statistic)
  243. }
  244. resp, err := svc.DescribeAlarmsForMetric(params)
  245. if err != nil {
  246. c.JsonApiErr(500, "Unable to call AWS API", err)
  247. return
  248. }
  249. c.JSON(200, resp)
  250. }
  251. func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
  252. cfg := getAwsConfig(req)
  253. svc := cloudwatch.New(session.New(cfg), cfg)
  254. reqParam := &struct {
  255. Parameters struct {
  256. AlarmName string `json:"alarmName"`
  257. HistoryItemType string `json:"historyItemType"`
  258. StartDate int64 `json:"startDate"`
  259. EndDate int64 `json:"endDate"`
  260. } `json:"parameters"`
  261. }{}
  262. json.Unmarshal(req.Body, reqParam)
  263. params := &cloudwatch.DescribeAlarmHistoryInput{
  264. AlarmName: aws.String(reqParam.Parameters.AlarmName),
  265. StartDate: aws.Time(time.Unix(reqParam.Parameters.StartDate, 0)),
  266. EndDate: aws.Time(time.Unix(reqParam.Parameters.EndDate, 0)),
  267. }
  268. if reqParam.Parameters.HistoryItemType != "" {
  269. params.HistoryItemType = aws.String(reqParam.Parameters.HistoryItemType)
  270. }
  271. resp, err := svc.DescribeAlarmHistory(params)
  272. if err != nil {
  273. c.JsonApiErr(500, "Unable to call AWS API", err)
  274. return
  275. }
  276. c.JSON(200, resp)
  277. }
  278. func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
  279. cfg := getAwsConfig(req)
  280. svc := ec2.New(session.New(cfg), cfg)
  281. reqParam := &struct {
  282. Parameters struct {
  283. Filters []*ec2.Filter `json:"filters"`
  284. InstanceIds []*string `json:"instanceIds"`
  285. } `json:"parameters"`
  286. }{}
  287. json.Unmarshal(req.Body, reqParam)
  288. params := &ec2.DescribeInstancesInput{}
  289. if len(reqParam.Parameters.Filters) > 0 {
  290. params.Filters = reqParam.Parameters.Filters
  291. }
  292. if len(reqParam.Parameters.InstanceIds) > 0 {
  293. params.InstanceIds = reqParam.Parameters.InstanceIds
  294. }
  295. var resp ec2.DescribeInstancesOutput
  296. err := svc.DescribeInstancesPages(params,
  297. func(page *ec2.DescribeInstancesOutput, lastPage bool) bool {
  298. reservations, _ := awsutil.ValuesAtPath(page, "Reservations")
  299. for _, reservation := range reservations {
  300. resp.Reservations = append(resp.Reservations, reservation.(*ec2.Reservation))
  301. }
  302. return !lastPage
  303. })
  304. if err != nil {
  305. c.JsonApiErr(500, "Unable to call AWS API", err)
  306. return
  307. }
  308. c.JSON(200, resp)
  309. }
  310. func HandleRequest(c *middleware.Context, ds *m.DataSource) {
  311. var req cwRequest
  312. req.Body, _ = ioutil.ReadAll(c.Req.Request.Body)
  313. req.DataSource = ds
  314. json.Unmarshal(req.Body, &req)
  315. if handler, found := actionHandlers[req.Action]; !found {
  316. c.JsonApiErr(500, "Unexpected AWS Action", errors.New(req.Action))
  317. return
  318. } else {
  319. handler(&req, c)
  320. }
  321. }