cloudwatch.go 11 KB

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